参考: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服务器再响应给浏览中器)java
这里与<使用OAuth2的SSO分析>主要不一样的是,accessToken是jwt,通过解码,转化就可成为认证受权信息,无需再向受权服务器协助得到认证受权信息,关于jwt可参看前面提供的连接.本文还修改了自定义登陆页和受权页,这种方案开始接近于生产了.git
一.先建立OAuth2受权服务器
1.由于使用了自定义页面,添加了wro4j-maven-plugin插件和如下依赖到pom.xmlgithub
<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同样是放在主类上. web
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:9999/uaa/oauth/token \ -d grant_type=authorization_code -d client_id=acme \ -d redirect_uri=http://example.com -d code=fjRdsL
回车获取access token,其中fjRdsL替换上步获取的受权许可码.返回结果相似以下: spring
{ "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,继续: json
[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.bash
最后运行主类的main方法测试(受权服务器前面启动了,access_token也获得了),因而在使用curl命令:
[root@dev ~]# curl -H “Authorization: Bearer $TOKEN” 192.168.1.115:9000
返回结果相似以下:
{ "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
微服务架构下,咱们的系统根据业务被拆分红了多个职责单一的微服务。
每一个服务都有本身的一套API提供给别的服务调用,那么如何保证安全性呢?
不是说你想调用就能够调用,必定要有认证机制,是咱们内部服务发出的请求,才能够调用咱们的接口。
须要注意的是咱们这边讲的是微服务之间调用的安全认证,不是统一的在API官网认证,需求不同,API网关处的统一认证是和业务挂钩的,咱们这边是为了防止接口被别人随便调用。
Spring Cloud可使用OAUTH2来实现多个微服务的统一认证受权
经过向OAUTH2服务进行集中认证和受权,得到access_token
而这个token是受其余微服务信任的,在后续的访问中都把access_token带过去,从而实现了微服务的统一认证受权。
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:
http://www.spring4all.com/article/356