Spring Security Oauth2 JWT、第三方登录、单点登录讲解,并使用Oauth2.0结合微服务进行单点登录
文章目录Oauth2.0Oauth2.0认证流程Oauth2.0在项目中的应用Spring security Oauth2认证解决方案Security Oauth2.0搭建认证服务器Oauth2授权模式授权码模式的实现令牌校验刷新令牌密码授权实现资源服务授权资源服务授权流程公钥私钥公钥私钥原理生成私钥公钥导出公钥测试小总结实战无对接网关对接网关权限配置Oauth2.0Oauth2.0认证流程官方书
文章目录
Oauth2.0
Oauth2.0认证流程
官方书籍:https://datatracker.ietf.org/doc/html/rfc6749
我们把
- Client想象成我们的YxinMiracle系统
- Resource Owner 资源拥有者
- Authorization Server 认证服务器(qq认证服务器)
- Resource Server 资源服务器(qq的用户微服务)
我的目的是:
- 获取Resource Server 资源服务器的资源
官方文档中表述的流程:
(A) 客户端向资源所有者请求授权。这
可以直接向资源所有者提出授权请求
(如图所示),或最好通过授权间接
服务器作为中介。
(B) 客户端收到授权许可,这是一个
代表资源所有者授权的凭证,
使用本文件中定义的四种授权类型之一表示
规范或使用扩展授权类型。这
授权授予类型取决于所使用的方法
客户端请求授权和支持的类型
授权服务器。
(C) 客户端通过身份验证请求访问令牌
授权服务器并提供授权许可。
(D) 授权服务器对客户端进行认证并验证
授权许可,如果有效,则颁发访问令牌。
(E) 客户端从资源中请求受保护的资源
服务器并通过提供访问令牌进行身份验证。
(F) 资源服务器验证访问令牌,如果有效,
服务于请求。
流程:
- 认证请求
- 认证授权
- 来到认证服务器,交出第二步拿到的授权码,向Authorization Server 认证服务器申请Token
- 拿到Token之后
- 将Token交给Resource Server申请资源
- 交还资源
Oauth2.0在项目中的应用
Oauth2是一个标准的开放的授权协议,应用程序可以根据自己的要求去使用Oauth2,本项目计划使用Oauth2实现如 下目标:
1、YxinMiracle系统访问第三方系统的资源
2、外部系统访问YxinMiracle系统的资源
3、YxinMiracle系统前端(客户端) 访问YxinMiracle系统微服务的资源
4、YxinMiracle系统微服务之间访问资源,例如:微服务A访问微服务B的资源,B访问A的资源
Spring security Oauth2认证解决方案
本项目采用 Spring security + Oauth2完成用户认证及用户授权,Spring security 是一个强大的和高度可定制的身份验证和访问控制框架,Spring security 框架集成了Oauth2协议,下图是项目认证架构图:
1、用户请求认证服务完成认证。
2、认证服务下发用户身份令牌,拥有身份令牌表示身份合法。
3、用户携带令牌请求资源服务,请求资源服务必先经过网关。
4、网关校验用户身份令牌的合法,不合法表示用户没有登录,如果合法则放行继续访问。
5、资源服务获取令牌,根据令牌完成授权。
6、资源服务完成授权则响应资源信息。
Security Oauth2.0
搭建认证服务器
认证服务器的文件结构:
加入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
配置文件:
server:
port: 9001
spring:
application:
name: user-auth
redis:
host: 192.168.211.132
port: 6379
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.211.132:3306/database?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
username: root
password: 123456
main:
allow-bean-definition-overriding: true
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
auth:
ttl: 3600 #token存储到redis的过期时间
clientId: yxinmiracle
clientSecret: yxinmiracle
cookieDomain: localhost
cookieMaxAge: -1
jks:
name: yxinmiracle.jks
storepassword: yxinmiracle
keypassword: yxinmiracle
alias: yxinmiracle
Oauth2授权模式
Oauth2有以下授权模式:
1.授权码模式(Authorization Code)
2.隐式授权模式(Implicit)
3.密码模式(Resource Owner Password Credentials)
4.客户端模式(Client Credentials)
其中常用的有:
授权码模式,密码模式,流程图如图1::
授权码模式的实现
此时我们可以启动认证服务器,访问连接:
Get请求:
http://localhost:9001/oauth/authorize?client_id=yxinmiracle&response_type=code&scop=app&redirect_uri=http://localhost
- 其中client_id表示的是那一个系统向认证系统发出请求
- response_type:code表示为授权码模式
- scop 表示的是使用范围
- redirect_uri 重定向到的url
访问之后会出现:
这就需要我们输入认证服务器系统的账号,利用认证服务器系统的账号登录我们的yxinmiracle系统,输入账号:
登录后会进行提示:
意思是:您确定要将认证服务器中的账号认证在yxinmiracle系统吗?这就相当于你们利用qq在别的平台登录一样,这个时候我们点击确认,就会返回一个授权码给我们,根据这个授权码我们可以向认证服务器请求TOKEN:
点击确认后重定向到:
http://localhost/?code=lfiS4M
其中lfiS4M
就是我们的授权码,我们可以根据这个授权码去获取Token
使用PostMan发送请求:
其中From表单中的数据是这样的:
KEY | VALUE |
---|---|
grant_type | authorization_code |
code | lfiS4M(获取到的授权码) |
redirect_uri | http://localhost |
我们的yxinmiracle系统与我们的认证系统也需要有一个认证的联系,这个联系是需要用过请求头中的信息关联的,也就是PostMan中的:
该请求会化作为请求头中的Authorization
,如下图所示:
发送请求:
数据(实际上就是一个jwt):
Key | 解释 |
---|---|
access_token | 令牌 |
token_type | 校验方式 |
refresh_token | 刷新令牌 |
expires_in | 过期时间 |
scope | 使用范围 |
jti | 唯一id |
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwNjEyODg0ODcsInVzZXJfbmFtZSI6InpoYW5nc2FuIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiJiMmNmYjMwYS0yNGU4LTQ2YWMtOGQ4ZC1lN2ZlYjFmZWJjNGQiLCJjbGllbnRfaWQiOiJjaGFuZ2dvdSIsInNjb3BlIjpbImFwcCJdfQ.UpSbU6xPVadYzl2sgzp2s43air8A5cd2wp11l0_U9BWj8TEtpnrj8PfTD_dMF7Al_hw_-kmeXvl4IX3F9wguUMBYmKYikJna8r0KsMUtA3f0YqD6lE6RcUv3EXfHb1WodTPeHUGLn55FEKDUJkVOzi0fYancCElACn9rMs3wEqhCj0AAs8_2Ee0Fg4wRtF9ZGCXuXZxnBrA-IYeJF5v7dL6w4Aa8YzcYuOmjmT3xrKH3FDDWnZql5YLrTlKEbNsR0AVQgQQXOBClym50zxOh_qtEkqMBCZQCIdkcDqFvpn8H7EdB46bx3GDI6rw_Xz_eLoMVt0qpzwx3KxYt7_ZhpQ",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFwcCJdLCJhdGkiOiJiMmNmYjMwYS0yNGU4LTQ2YWMtOGQ4ZC1lN2ZlYjFmZWJjNGQiLCJleHAiOjIwNjEyODg0ODcsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiXSwianRpIjoiYmQ0ZjJmOTMtNTA3ZC00NWQzLWFlOGQtZGVlMWY5M2ExMTFlIiwiY2xpZW50X2lkIjoiY2hhbmdnb3UifQ.IVdKYliRDRJPf6bMxrx0XYDBFU00oOAJLjLC99tbkJIcJ75wKZSMdJU9exLhiqZ22ZLVBaI2KiMdSCP4DRAYuqV7DFL31c3CTsNEOrWktRgbK_z_T4RP_JIoK3pNPWk5uxcZXM6x2rnMU_n6Lu3_CYBh5xr3DC9a5HXtkmmpKzf7S3lXzm4Hq5DJKlqcnhRLLF97IDrqPUkXvcAbErdh0kKKY_bB1MwMl-ascBU8SKBPEFn96-nY0u0Fd4i0kAtQlnQ2TJFSIsOE8GQaKswYaNNJHh2JlyegiI1ByfPEu-iIDpKJv4OxZsMAnh2HmmLeWJbjf-0AhFpyaFtEcPsIZg",
"expires_in": 431999999,
"scope": "app",
"jti": "b2cfb30a-24e8-46ac-8d8d-e7feb1febc4d"
}
令牌校验
Spring Security Oauth2提供校验令牌的端点,如下:
Get: http://localhost:9001/oauth/check_token?token=
参数:
token:令牌
通过postman发送请求:
结果为:
{
"user_name": "zhangsan",
"scope": [
"app"
],
"active": true,
"exp": 2061288487,
"authorities": [
"ROLE_ADMIN",
"ROLE_USER"
],
"jti": "b2cfb30a-24e8-46ac-8d8d-e7feb1febc4d",
"client_id": "yxinmiracle"
}
刷新令牌
刷新令牌是当令牌快过期时重新生成一个令牌,它于授权码授权和密码授权生成令牌不同,刷新令牌不需要授权码 也不需要账号和密码,只需要一个刷新令牌、客户端id和客户端密码。
测试如下: Post:http://localhost:9001/oauth/token
参数:
grant_type: 固定为 refresh_token
refresh_token:刷新令牌(注意不是access_token,而是refresh_token)
密码授权实现
(1)认证
密码模式(Resource Owner Password Credentials)与授权码模式的区别是申请令牌不再使用授权码,而是直接 通过用户名和密码即可申请令牌。
测试如下:
Post请求:http://localhost:9001/oauth/token
参数:
grant_type:密码模式授权填写password
username:账号
password:密码
并且此链接需要使用 http Basic认证。
(2)校验令牌
Spring Security Oauth2提供校验令牌的端点,如下:
Get: http://localhost:9001/oauth/check_token?token=
参数:
token:令牌
使用postman测试如下:
exp:过期时间,long类型,距离1970年的秒数(new Date().getTime()可得到当前时间距离1970年的毫秒数)。
user_name: 用户名
client_id:客户端Id,在oauth_client_details中配置
scope:客户端范围,在oauth_client_details表中配置
jti:与令牌对应的唯一标识 companyId、userpic、name、utype、
id:这些字段是本认证服务在Spring Security基础上扩展的用户身份信息
资源服务授权
资源服务授权流程
(1)传统授权流程
资源服务器授权流程如上图,客户端先去授权服务器申请令牌,申请令牌后,携带令牌访问资源服务器,资源服务器访问授权服务校验令牌的合法性,授权服务会返回校验结果,如果校验成功会返回用户信息给资源服务器,资源服务器如果接收到的校验结果通过了,则返回资源给客户端。
传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性,并根 据令牌获取用户的相关信息,性能低下。
(2)公钥私钥授权流程
传统的授权模式性能低下,每次都需要请求授权服务校验令牌合法性,我们可以利用公钥私钥完成对令牌的加密,如果加密解密成功,则表示令牌合法,如果加密解密失败,则表示令牌无效不合法,合法则允许访问资源服务器的资源,解密失败,则不允许访问资源服务器资源。
上图的业务流程如下:
1、客户端请求认证服务申请令牌
2、认证服务生成令牌认证服务采用非对称加密算法,使用私钥生成令牌。
3、客户端携带令牌访问资源服务客户端在Http header 中添加: Authorization:Bearer 令牌。
4、资源服务请求认证服务校验令牌的有效性资源服务接收到令牌,使用公钥校验令牌的合法性。
5、令牌有效,资源服务向客户端响应资源信息
公钥私钥
在对称加密的时代,加密和解密用的是同一个密钥,这个密钥既用于加密,又用于解密。这样做有一个明显的缺点,如果两个人之间传输文件,两个人都要知道密钥,如果是三个人呢,五个人呢?于是就产生了非对称加密,用一个密钥进行加密(公钥),用另一个密钥进行解密(私钥)。
公钥私钥原理
张三有两把钥匙,一把是公钥,另一把是私钥。
张三把公钥送给他的朋友们----李四、王五、赵六----每人一把。
李四要给张三写一封保密的信。她写完后用张三的公钥加密,就可以达到保密的效果。
张三收信后,用私钥解密,就看到了信件内容。这里要强调的是,只要张三的私钥不泄露,这封信就是安全的,即使落在别人手里,也无法解密。
张三给李四回信,决定采用"数字签名"。他写完后先用Hash函数,生成信件的摘要(digest)。张三将这个签名,附在信件下面,一起发给李四。
李四收信后,取下数字签名,用张三的公钥解密,得到信件的摘要。由此证明,这封信确实是张三发出的。李四再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。
生成私钥公钥
Spring Security 提供对JWT的支持,本节我们使用Spring Security 提供的JwtHelper来创建JWT令牌,校验JWT令牌 等操作。 这里JWT令牌我们采用非对称算法进行加密,所以我们要先生成公钥和私钥。
(1)生成密钥证书 下边命令生成密钥证书,采用RSA 算法每个证书包含公钥和私钥
创建一个文件夹,在该文件夹下执行如下命令行:
keytool -genkeypair -alias yxinmiracle -keyalg RSA -keypass yxinmiracle -keystore yxinmiracle.jks -storepass yxinmiracle
Keytool 是一个java提供的证书管理工具
-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名,xc.keystore保存了生成的证书
-storepass:密钥库的访问密码
使用如图:
之后文件夹中会出现:
(2)查询证书信息
keytool -list -keystore yxinmiracle.jks
(3)删除别名
keytool -delete -alias yxinmiracle -keystore yxinmiracle.jsk
导出公钥
openssl是一个加解密工具包,这里使用openssl来导出公钥信息。
下载地址:http://slproweb.com/products/Win32OpenSSL.html
选择:
安装完后进行环境变量的添加
cmd进入yxinmiracle.jks文件所在目录执行如下命令(如下命令在windows下执行,会把-变成中文方式,请将它改成英文的-):
keytool -list -rfc --keystore yxinmiracle.jks | openssl x509 -inform pem -pubkey
结果:
下面段内容是公钥
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlTaqLMvTUHgaAdffduem
niZ2hee/lKmZ32rDS/862fbLuX4LdHWjkZrrWV2MzFrIYeu3zRr7QxCQy/rtvDKk
f9OKd+K3d2dJ8ljjVwmOAVI48BlBoZU66Ugu8oSepXWWP7d5sJcpCUzfUJc3AlZE
tvpttG+YJjOp2dI4rb7AAK1YD+kw6wi47rmCneYhQUtIq3pCqe/Owudceq0JOH40
R3IvXfsv/YHi5tFENT+f+Q/KMlFVWedRzMyBR12FvzGXiwYlYjcNbb8wAp3UWyuy
HinrbTI90EwjawUKMsjEbirKfN0JSjIdpg5J5EEMUk0XVaqMZZ3RPiRwFOTXmnkv
6wIDAQAB
-----END PUBLIC KEY-----
测试
创建令牌:
package com.yxinmiracle.token;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.util.HashMap;
import java.util.Map;
public class CreateJwtTest {
/***
* 创建令牌测试
*/
@Test
public void testCreateToken(){
//证书文件路径
String key_location="yxinmiracle.jks";
//秘钥库密码
String key_password="yxinmiracle";
//秘钥密码
String keypwd = "yxinmiracle";
//秘钥别名
String alias = "yxinmiracle";
//访问证书路径
ClassPathResource resource = new ClassPathResource(key_location);
//创建秘钥工厂
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource,key_password.toCharArray());
//读取秘钥对(公钥、私钥)
KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias,keypwd.toCharArray());
//获取私钥
RSAPrivateKey rsaPrivate = (RSAPrivateKey) keyPair.getPrivate();
//定义Payload
Map<String, Object> tokenMap = new HashMap<>();
tokenMap.put("id", "1");
tokenMap.put("name", "YxinMiracle");
tokenMap.put("roles", "ROLE_VIP,ROLE_USER");
//生成Jwt令牌
Jwt jwt = JwtHelper.encode(JSON.toJSONString(tokenMap), new RsaSigner(rsaPrivate)); // 要用私钥来作为秘钥放到jwt中创造令牌
//取出令牌
String encoded = jwt.getEncoded();
System.out.println(encoded);
}
}
结果:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6IlJPTEVfVklQLFJPTEVfVVNFUiIsIm5hbWUiOiJZeGluTWlyYWNsZSIsImlkIjoiMSJ9.gXlwor6RS4MkFKtm9wFY9hbzYZmT-OthHZ6cCbWdr50KIOw_MESCDgVFkkL_4PFwBI8unIVYAD5JM_4Fkr3KmrkjObhCkyANe_lhfWHzvBZWUpK2Wsc2B2NF3juePeEUHTGbSIwVbQng0CKNn_3vCK6IIxRMrZUBcqqBbtt6cKawtMK_CzOX3JpI3nqmAf7_5oq3aTKM0l0Lp1AsHiHZiJ_Umk9lqg86KxeDhrJjuS5cQffRGrPNQhs-0HrgqtBsyqvqBp8XHo8J82Sc_rdABF4R1Ynoye4tKDTg6TCnWcFgD3MkyNs5caHy0uch3bHjuYsV44JrnjEUCvWH85GVUA
解析令牌:
package com.yxinmiracle.token;
import org.junit.Test;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
public class ParseJwtTest {
/***
* 校验令牌
*/
@Test
public void testParseToken(){
//令牌
String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6IlJPTEVfVklQLFJPTEVfVVNFUiIsIm5hbWUiOiJZeGluTWlyYWNsZSIsImlkIjoiMSJ9.gXlwor6RS4MkFKtm9wFY9hbzYZmT-OthHZ6cCbWdr50KIOw_MESCDgVFkkL_4PFwBI8unIVYAD5JM_4Fkr3KmrkjObhCkyANe_lhfWHzvBZWUpK2Wsc2B2NF3juePeEUHTGbSIwVbQng0CKNn_3vCK6IIxRMrZUBcqqBbtt6cKawtMK_CzOX3JpI3nqmAf7_5oq3aTKM0l0Lp1AsHiHZiJ_Umk9lqg86KxeDhrJjuS5cQffRGrPNQhs-0HrgqtBsyqvqBp8XHo8J82Sc_rdABF4R1Ynoye4tKDTg6TCnWcFgD3MkyNs5caHy0uch3bHjuYsV44JrnjEUCvWH85GVUA";
//公钥
String publickey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlTaqLMvTUHgaAdffduemniZ2hee/lKmZ32rDS/862fbLuX4LdHWjkZrrWV2MzFrIYeu3zRr7QxCQy/rtvDKkf9OKd+K3d2dJ8ljjVwmOAVI48BlBoZU66Ugu8oSepXWWP7d5sJcpCUzfUJc3AlZEtvpttG+YJjOp2dI4rb7AAK1YD+kw6wi47rmCneYhQUtIq3pCqe/Owudceq0JOH40R3IvXfsv/YHi5tFENT+f+Q/KMlFVWedRzMyBR12FvzGXiwYlYjcNbb8wAp3UWyuyHinrbTI90EwjawUKMsjEbirKfN0JSjIdpg5J5EEMUk0XVaqMZZ3RPiRwFOTXmnkv6wIDAQAB-----END PUBLIC KEY-----";
//校验Jwt
Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey));
//获取Jwt原始内容
String claims = jwt.getClaims();
System.out.println(claims);
//jwt令牌
String encoded = jwt.getEncoded();
System.out.println(encoded);
}
}
结果:
小总结
当我们访问/oauth/token
生成的令牌是通过这个方法生成的:
/**
*
* 创建令牌的方法
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
//设置jwt的转换器 必须要有
JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 使用jwt的方式创建令牌
//设置jwt的秘钥库
//秘钥库的名称
String keystorepath =jksProperties.getName(); // 从配置文件中读取配置
//秘钥库的密码
String storepassword = jksProperties.getStorepassword();
//读取秘钥对的密码
String keypassword = jksProperties.getKeypassword();
//秘钥库的别名
String alias = jksProperties.getAlias();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource(keystorepath), //设置加密的加载文件
storepassword.toCharArray())//设置读取秘钥库文件的密码
.getKeyPair(alias, keypassword.toCharArray());//设置获取秘钥的密码
//设置秘钥对象
converter.setKeyPair(keyPair);
//使用JWT的令牌转换器
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
return converter;
}
实战
用户访问其他功能微服务时,必须判断该用户有没有登录,如果有并且写到的token是正确的(正不正确可以使用该功能微服务中的公钥与用户请求携带来的token进行校验,如果说校验成功那么就是登录过的)
首先我们要登录的话就需要访问我们的user微服务
无对接网关
在user微服务中添加一个配置类,并且把公钥放在user微服务的类路径下:
项目结构代码:
配置类代码:
package com.yxinmiracle.user.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Base64;
import java.util.stream.Collectors;
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-08-17 16:41
*/
@Configuration
@EnableResourceServer // 标识这是一台资源服务器
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY = "public.key";
/***
* 定义JwtTokenStore
* @param jwtAccessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/***
* 定义JJwtAccessTokenConverter 转化器 既能创建令牌 也能解析令牌 用来配置公钥
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey()); // setVerifierKey需要的是一个字符串信息 getPubKey()得到文件中的公钥
return converter;
}
/**
* 获取非对称加密公钥 Key
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
/***
* Http安全配置,对每个到达系统的http请求链接进行校验
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers(
"/user/add","/user/load/{id}"). //配置地址放行 用户注册是需要放行的
permitAll()
.anyRequest().
authenticated(); //其他地址需要认证授权
}
}
此时在没有携带令牌的令牌的情况下进行访问(此时没有开启网关),可以发现没有权限:
此时我们可以进行登录:
然后携带令牌进行访问,但是在访问的时候需要在请求头中添加上Authorization
字段,key为bearer Token
,如:
再进行请求访问,发现请求成功:
小插曲
为什么是bearer呢?
因为在我们生成令牌的时候,可以发现有一个token类型,是需要bearer方式的。
对接网关
经过网关的方式发送请求,发现网关并不能将用户的令牌携带到微服务去,如图报了401无权限的错误:
解决办法:
在网关内,我们需要将这个数据添加到请求头中,再传到下一个微服务中:
成功请求:
权限配置
在user微服务中编写一个测试方法,只有角色有“ROLE_XXX”的人才能访问这个路径
可以发现在用户登录的时候我们都会给他配置权限:
在进行Token解析的时候也能看见用户拥有什么样的权限:
{
"user_name": "zhangsan",
"scope": [
"app"
],
"active": true,
"exp": 2061288487,
"authorities": [
"ROLE_ADMIN",
"ROLE_USER"
],
"jti": "b2cfb30a-24e8-46ac-8d8d-e7feb1febc4d",
"client_id": "yxinmiracle"
}
进行访问,可以发现并不能访问:
将权限设置为ROLE_ADMIN后:
进行访问,成功访问:
更多推荐
所有评论(0)