参考: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

  1. <dependency>
  2. <groupId>org.springframework.security</groupId>
  3. <artifactId>spring-security-jwt</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-freemarker</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-actuator</artifactId>
  12. </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输入

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

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

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

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

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

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

  1. {
  2. "details": {
  3. "remoteAddress": "192.168.1.194",
  4. "sessionId": null,
  5. "tokenValue": "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0NTk1NTUxNTYsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sImp0aSI6IjI5MjcyYWJiLTQ4MjUtNGYwMS1hZjllLTg5ZGE1ZDE1MDBiNyIsImNsaWVudF9pZCI6ImFjbWUiLCJzY29wZSI6WyJvcGVuaWQiXX0.cQd88GYItHUDJuwkd_Rd0Yo8QM1R0dccuK0-xZ4OynC7EnqClLunaNOZ9jXwtilIFJNxbkbhQ8ymXdvlAF5Zjo8lpRGotdVo9rgQc39BDse7hGy1EfA9ZADQmJ-EuwkTNo0IBEXYC33XxQNK_3I_E92cnIPXq-FZHuZMRzpr-SlriwLa3aZVidmeyXK2U5dsjViWoHHKhcg-9c-VBPtyTJfPZOvj3s7DrbfCgOAGOhHkd_MBCdLDFb7QFhzIRsMfcD9rOAGTqk-hU2pHkkakKQ7_vL604UU7Qh3Zzkn6VbHPy0HAAiB9cnUhkQxK3Qb-wbHG-l3FC2pDlhtlhMHNfg",
  6. "tokenType": "Bearer",
  7. "decodedDetails": null
  8. },
  9. "authorities": [
  10. {
  11. "authority": "ROLE_ADMIN"
  12. },
  13. {
  14. "authority": "ROLE_USER"
  15. }
  16. ],
  17. "authenticated": true,
  18. "userAuthentication": {
  19. "details": null,
  20. "authorities": [
  21. {
  22. "authority": "ROLE_ADMIN"
  23. },
  24. {
  25. "authority": "ROLE_USER"
  26. }
  27. ],
  28. "authenticated": true,
  29. "principal": "user",
  30. "credentials": "N/A",
  31. "name": "user"
  32. },
  33. "credentials": "",
  34. "principal": "user",
  35. "oauth2Request": {
  36. "clientId": "acme",
  37. "scope": [
  38. "openid"
  39. ],
  40. "requestParameters": {
  41. "client_id": "acme"
  42. },
  43. "resourceIds": [],
  44. "authorities": [],
  45. "approved": true,
  46. "refresh": false,
  47. "redirectUri": null,
  48. "responseTypes": [],
  49. "extensions": {},
  50. "grantType": null,
  51. "refreshTokenRequest": null
  52. },
  53. "clientOnly": false,
  54. "name": "user"
  55. }

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

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

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

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

返回结果类似如下:

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

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

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

  1. org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter
  2. #doFilter{
  3. Authentication authentication = tokenExtractor.extract(request);//抽取Token
  4. Authentication authResult = authenticationManager.authenticate(authentication);//还原解码认证授权信息
  5. }
  6. org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager
  7. #authenticate{
  8. OAuth2Authentication auth = tokenServices.loadAuthentication(token);//这里的tokenServices是DefaultTokenServices
  9. }
  10. org.springframework.security.oauth2.provider.token.DefaultTokenServices
  11. #loadAuthentication{
  12. OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);//tokenStore是JwtTokenStore
  13. OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
  14. }
  15. org.springframework.security.oauth2.provider.token.store.JwtTokenStore
  16. #readAccessToken{
  17. OAuth2AccessToken accessToken = convertAccessToken(tokenValue);
  18. }
  19. org.springframework.security.oauth2.provider.token.store.JwtTokenStore
  20. #convertAccessToken{
  21. return jwtTokenEnhancer.extractAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
  22. }
  23. org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter
  24. #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

  1. @RestController
  2. @RequestMapping(value="/oauth")
  3. public class AuthController {
  4.  
  5. @Autowired
  6. private AuthService authService;
  7.  
  8. @PostMapping("/token")
  9. public ResponseData auth(@RequestBody AuthQuery query) throws Exception {
  10. if (StringUtils.isBlank(query.getAccessKey()) || StringUtils.isBlank(query.getSecretKey())) {
  11. return ResponseData.failByParam("accessKey and secretKey not null");
  12. }
  13.  
  14. User user = authService.auth(query);
  15. if (user == null) {
  16. return ResponseData.failByParam("认证失败");
  17. }
  18.  
  19. JWTUtils jwt = JWTUtils.getInstance();
  20. return ResponseData.ok(jwt.getToken(user.getId().toString()));
  21. }
  22.  
  23. @GetMapping("/token")
  24. public ResponseData oauth(AuthQuery query) throws Exception {
  25. if (StringUtils.isBlank(query.getAccessKey()) || StringUtils.isBlank(query.getSecretKey())) {
  26. return ResponseData.failByParam("accessKey and secretKey not null");
  27. }
  28.  
  29. User user = authService.auth(query);
  30. if (user == null) {
  31. return ResponseData.failByParam("认证失败");
  32. }
  33.  
  34. JWTUtils jwt = JWTUtils.getInstance();
  35. return ResponseData.ok(jwt.getToken(user.getId().toString()));
  36. }
  37.  
  38. }

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

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

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

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

  1. <dependency>
  2. <groupId>io.jsonwebtoken</groupId>
  3. <artifactId>jjwt</artifactId>
  4. <version>0.7.0</version>
  5. </dependency>

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

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

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

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

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

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

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

  1. //验证TOKEN
  2. if (!StringUtils.hasText(auth)) {
  3. PrintWriter print = httpResponse.getWriter();
  4. print.write(JsonUtils.toJson(ResponseData.fail("非法请求【缺少Authorization信息】",
  5. ResponseCode.NO_AUTH_CODE.getCode())));
  6. return;
  7. }
  8. JWTUtils.JWTResult jwt = jwtUtils.checkToken(auth);
  9. if (!jwt.isStatus()) {
  10. PrintWriter print = httpResponse.getWriter();
  11. print.write(JsonUtils.toJson(ResponseData.fail(jwt.getMsg(), jwt.getCode())));
  12. return;
  13. }
  14. 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. 文件lseek操作产生空洞文件的方法

    在文件操作过程中,lseek操作可以偏移到文件的任意位置. 在UNIX文件操作中,文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞,这一点是允许的 ...

  2. Redis配置信息

    # Redis configuration file example # Note on units: when memory size is needed, it is possible to sp ...

  3. Java Socket:Java-NIO-Selector

    Selector 的出现,大大改善了多个 Java Socket的效率.在没有NIO的时候,轮询多个socket是通过read阻塞来完成,即使是非阻塞模式,我们在轮询socket是否就绪的时候依然需要 ...

  4. The 2nd tip of DB Query Analyzer

    The 2nd tip of DB Query Analyzer                               Ma Genfeng   (Guangdong Unitoll Servi ...

  5. 关于java和c++中布尔量的比较

    在c++中允许 bool 量和 int 整形常量相互转换,并且用cout<<true; 在控制台上可以输出为 1 int main(int argc, _TCHAR* argv[]) { ...

  6. miniUI中弹出框问题

    ---恢复内容开始--- 设置页面弹出框并提交弹出框内容 弹出按钮 <a class="btn_color_1" onclick="onEdit(0)"& ...

  7. oracle超出打开游标的最大数的原因和解决方案

    oracle超出打开游标的最大数的原因和解决方案 分类: Oracle相关2012-06-05 10:36 6362人阅读 评论(0) 收藏 举报 oracle数据库sqljavasessionsys ...

  8. JTA 分布式事务

    什么是JTA - 2009-07-25 18:31:06|  分类: 技术文章|举报|字号 订阅     什么是JTA? Java Transaction API(Java事务API) (JTA)Ja ...

  9. LOVO学习之思维导图和文档编辑器

    思维导图——是一种图示笔记方法,一种图示笔记工具,一个思考的利器.能将放射性思考具体化,帮助人们理解和记忆事物. 思维导图绘制规则:1,在纸的正中央用一个彩色图像或者符号开始画思维导图. 2,把所有主 ...

  10. Python2和Python3比较分析

    一直有看到网上有讨论Python2和Python3的比较,最近公司也在考虑是否在spark-python大数据开发环境中升级到python3.通过本篇博文记录Python2.7.13和Pthon3.5 ...