针对JWT简介与原理,代码实例,以及oauth2+JWT+RSA的集成配置
JWT简介
- JWT 是基于 RFC 7519 标准定义的一种可以安全传输的规范, 这个规范允许我们使用 JWT 在前后端之间传递安全可靠的信息。
- JWT 由于使用了数字签名,所以是可信任和安全的。
通过 session 管理用户登录状态成本越来越高,使用无状态的 token 的方式做登录身份校验显得越来越流行,不仅可以减轻服务器的压力, 由于 token 是在授权头(Authorization header)中发送的,因此做单点登陆的时候还没有cookie跨域问题.
而 JWT(java web token) 就是目前最流行的单点登录跨域身份验证解决方案。
JWT 组成
一个 JWT 实际上就是一个字符串,未加密前的 jwt 就是一个json
, 它由头部、载荷与签名三部分组成。
header(头部): 用于描述关于该 JWT 的最基本的信息,例如其类型以及签名所用的算法等。
{"typ": "JWT","alg": "HS256" }
header 主要包含两个部分,
alg
指加密类型,可选值为HS256
、RSA
等等,typ=JWT
为固定值,表示 token 的类型。payload(载荷): 载荷就是存放有效信息的地方。
{"sub": "1340502208","name": "John Doe","role": "admin" }
官方定义的 payload 一般包括以下内容
- iss:Issuer,发行者
- sub:Subject,主题
- aud:Audience,观众
- exp:Expiration time,过期时间
- nbf:Not before
- iat:Issued at,发行时间
- jti:JWT ID
signature(签证): jwt 的第三部分是一个签证信息,这个签证信息由三部分组成
- 头部经 base64 编码后的字符
- 是载荷经 base64 编码后的字符
- 是盐(密钥),通常存于服务器
之后将
1
和2
用.
连接,通过头部中声明的加密算法进行加盐(3)计算,得到第三部分, 伪代码如下.EncodeString = Base64(header) + "." + Base64(payload) token = HS256(EncodeString, "秘钥")
签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的 token,它还可以验证 JWT 的发送方是否为它所称的发送方。
下面是一个JWT示例, 对应的密钥是
123456
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0ODIzIiwic3ViIjoiQ1BGIiwiaWF0IjoxNjQzNDY1MzM3LCJleHAiOjE2NDM0Njg5MzcsInJvbGVzIjoiYWRtaW4sdXNlciIsImVtYWlsIjoiMTIzQG9oLmNvbSJ9.JBmv97KOYYNRsKKcHpy26uLPN6JrwJzV_WdqzIpwEx0
可以去 https://jwt.io/ 上面去验证解析.
JWT 工作原理
首先, 用户登录后, 服务器向前台发送一个 JWT.
用户想要访问受保护的路由或者资源的时候,应当携带
JWT
,通常放在请求头中的Authorization
上, 如Bearer token
服务器的路由或网关去检查 Authorization header 中的 JWT 是否有效,如果有效,则用户可以访问受保护的资源。
JWT 验证过程
签名验证
服务器获取到一个 JWT 的时候,首先要对这个 JWT 的完整性进行验证,这个就是签名认证。
验证方式如下:
截开 JWT 前缀, 根据上面内容来看是
Bearer
, 获取到token
.分离
token
为 header, payload, signature.对 header 做 base64 解码, 获得 JWT 使用的算法编码.
使用后端保存的密钥, 和得到的编码对 header, payload 进行签证计算, 算出 signature.
如 header 使用的编码为
HS256
. 则执行以下伪代码.EncodeString = Base64(header) + "." + Base64(payload) token = HS256(EncodeString, "秘钥")
接收方生成签名的时候必须使用跟 JWT 发送方相同的密钥,意味着要做好密钥的安全传递或共享
将算出的 signature 与 token 带的 signature 进行对比, 若完全相同, 表明信息是正确的. 若不同,就可以认为这个 JWT 是一个被篡改过的串,自然就属于验证失败了。
载体验证
在验证一个 JWT 的时候,签名认证是每个实现库都会自动做的,但是 payload 的认证是由使用者来决定的。由于载体里面存放的内容根据需求各有不同, 因此, 载体验证一般需要由使用者亲自完成.
以下是官方的载体验证规范.
- iss(Issuser):如果签发的时候这个 claim 的值是“a.com”,验证的时候如果这个 claim 的值不是“a.com”就属于验证失败
- sub(Subject):如果签发的时候这个 claim 的值是“liuyunzhuge”,验证的时候如果这个 claim 的值不是“liuyunzhuge”就属于验证失败
- aud(Audience):如果签发的时候这个 claim 的值是“[‘b.com’,‘c.com’]”,验证的时候这个 claim 的值至少要包含 b.com,c.com 的其中- 一个才能验证通过
- exp(Expiration time):如果验证的时候超过了这个 claim 指定的时间,就属于验证失败;nbf(Not Before):如果验证的时候小于这- 个 claim 指定的时间,就属于验证失败
- iat(Issued at):它可以用来做一些 maxAge 之类的验证,假如验证时间与这个 claim 指定的时间相差的时间大于通过 maxAge 指定的一个- 值,就属于验证失败
- jti(JWT ID):如果签发的时候这个 claim 的值是“1”,验证的时候如果这个 claim 的值不是“1”就属于验证失败
以登录认证来说,在签发 JWT 的时候,完全可以只用 sub 跟 exp 两个 claim,用 sub 存储用户的 id,用 exp 存储它本次登录之后的过期时间,然后在验证的时候仅验证 exp 这个 claim,以实现会话的有效期管理。
JWT 生成与解析代码实例
引入依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version> </dependency>
编写测试类
import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import java.util.Date;public class JwtTest {@Testpublic void testJwt() throws InterruptedException {Date date = new Date();final String encodeString = "123456";//生成jwt令牌JwtBuilder jwtBuilder = Jwts.builder()// 设置载荷 : jwt编码, 对象, 签发日期, 过期时间.setId("4823").setSubject("CPF").setIssuedAt(date).setExpiration(new Date(date.getTime() + 3600 * 1000))// 设置自定义的载荷.claim("roles","admin,user").claim("email","123@oh.com")// 使用 HS256 对称加密算法签名, 第二个参数为秘钥.signWith(SignatureAlgorithm.HS256, encodeString);// 生成 jwtfinal String jwtToken = jwtBuilder.compact();System.out.println("生成的JWT: " + jwtToken);// 解析 jwtfinal Claims claims = Jwts.parser().setSigningKey(encodeString).parseClaimsJws(jwtToken).getBody();System.out.println("解析的内容: " + claims);// 更换一个 IDjwtBuilder.setId("1234");final String jwtToken2 = jwtBuilder.compact();System.out.println("\n替换id后的Jwt: " + jwtToken2);final String[] split1 = jwtToken.split("\\.");final String[] split2 = jwtToken2.split("\\.");final String errToken = split1[0] + "." + split2[1] + "." + split1[2];System.out.println("将两个JWT的载荷进行拼接JWT : " + errToken);System.out.println("\n然后就发现解析会失败!");// 解析 jwtJwts.parser().setSigningKey(encodeString).parseClaimsJws(errToken).getBody();}}
输出
生成的JWT: eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0ODIzIiwic3ViIjoiQ1BGIiwiaWF0IjoxNjQzNDQ4MzEyLCJleHAiOjE2NDM0NTE5MTIsInJvbGVzIjoiYWRtaW4sdXNlciIsImVtYWlsIjoiMTIzQG9oLmNvbSJ9.h-0EDrWj7Lnqfz7Jnjt0DKrGwvdicaA8BKlxJroYWB0 解析的内容: {jti=4823, sub=CPF, iat=1643448312, exp=1643451912, roles=admin,user, email=123@oh.com}替换id后的Jwt: eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0Iiwic3ViIjoiQ1BGIiwiaWF0IjoxNjQzNDQ4MzEyLCJleHAiOjE2NDM0NTE5MTIsInJvbGVzIjoiYWRtaW4sdXNlciIsImVtYWlsIjoiMTIzQG9oLmNvbSJ9.4cf8EQO1Vfm9Bcu2GCIJGlAt4eKbqb_Q_26-tfFUHao 将两个JWT的载荷进行拼接JWT : eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0Iiwic3ViIjoiQ1BGIiwiaWF0IjoxNjQzNDQ4MzEyLCJleHAiOjE2NDM0NTE5MTIsInJvbGVzIjoiYWRtaW4sdXNlciIsImVtYWlsIjoiMTIzQG9oLmNvbSJ9.h-0EDrWj7Lnqfz7Jnjt0DKrGwvdicaA8BKlxJroYWB0然后就发现解析会失败!Exception in thread "main" io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:354)at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481)at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541)
JWT 使用 RSA 加密实例
相对于 HS256
对称加密, RSA
非对称加密更加安全一些.
为了生成 JWT Token 我们还需要使用 RSA 算法来进行签名。这里我们使用 JDK 提供的证书管理工具 Keytool 来生成 RSA 证书 ,格式为 jks
格式。
JDK-keytool 生成证书命令参考:
keytool -genkey -alias felordcn -keypass felordcn -keyalg RSA -storetype PKCS12 -keysize 1024 -validity 365 -keystore d:/keystores/felordcn.jks -storepass 123456 -dname "CN=(Felord), OU=(felordcn), O=(felordcn), L=(zz), ST=(hn), C=(cn)"
命令参数详解
参数 | 默认 | 含义 |
---|---|---|
genkey | 在用户主目录中创建一个默认文件".keystore",还会产生一个 mykey 的别名,mykey 中包含用户的公钥、私钥和证书 | |
alias |
mykey
|
产生别名 |
keystore |
用户系统默认目录
|
指定密钥库的名称(产生的各类信息将不在.keystore 文件中) |
keyalg |
DSA
|
指定密钥的算法 (如 RSA DSA(如果不指定默认采用 DSA)) |
validity | 指定创建的证书有效期多少天 | |
keysize |
1024
|
指定密钥长度 |
storepass | 指定密钥库的密码(获取 keystore 信息所需的密码) | |
keypass | 指定别名条目的密码(私钥的密码) | |
dname | 指定证书拥有者信息 例如: “CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=单位的两字母国家代码” | |
list | 显示密钥库中的证书信息 keytool -list -v -keystore 指定 keystore -storepass 密码 | |
v | 显示密钥库中的证书详细信息 | |
export | 将别名指定的证书导出到文件 keytool -export -alias 需要导出的别名 -keystore 指定 keystore -file 指定导出的证书位置及证书名称 -storepass 密码 | |
file | 参数指定导出到文件的文件名 | |
delete | 删除密钥库中某条目 keytool -delete -alias 指定需删除的别 -keystore 指定 keystore -storepass 密码 | |
printcert | 查看导出的证书信息 keytool -printcert -file yushan.crt | |
keypasswd | 修改密钥库中指定条目口令 keytool -keypasswd -alias 需修改的别名 -keypass 旧密码 -new 新密码 -storepass keystore 密码 -keystore sage | |
storepasswd | 修改 keystore 口令 keytool -storepasswd -keystore e:/yushan.keystore(需修改口令的 keystore) -storepass 123456(原始密码) -new yushan(新密码) | |
import | 将已签名数字证书导入密钥库 keytool -import -alias 指定导入条目的别名 -keystore 指定 keystore -file 需导入的证书 |
keytool 生成命令实操
用 JDK 中的 keytool 生成 RSA 证书, 生成 rsa.jws
keytool -genkey -keyalg RSA -alias oh -keypass 212003 -storepass 456123 -keystore P:\git\rsa.jws
其中
-alias oh -keypass 212003 -storepass 456123
我们要作为配置使用要记下来。我们要使用下面定义的这个类来读取证书- 在控制台输入之后, 会提示
名字与姓氏
, 组织名称之类的乱七八糟的东西, 先填就填, 不填可以直接回车通过, 最后在否的地方输入个y
就可以. - 最后还会提示 迁移到行业标准格式 PKCS12 之类的, 可以不用管,
D:\programing\sdk\jdk-current\bin>keytool -genkey -keyalg RSA -alias oh -keypass 212003 -storepass 456123 -keystore P:\git\rsa.jws 您的名字与姓氏是什么? [Unknown]: chen 您的组织单位名称是什么? [Unknown]: hydroxyl 您的组织名称是什么? [Unknown]: 您所在的城市或区域名称是什么? [Unknown]: 您所在的省/市/自治区名称是什么? [Unknown]: 该单位的双字母国家/地区代码是什么? [Unknown]: CN=chen, OU=hydroxy, O=Unknown, L=Unknown, ST=Unknown, C=Unknown是否正确? [否]: yWarning: JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore P:\git\rsa.jws -destkeystore P:\git\rsa.jws -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。
- 在控制台输入之后, 会提示
通过代码去验证生成的
rsa.jwt
文件是否能正常使用import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.FileInputStream; import java.security.*; import java.security.cert.Certificate; import java.util.Base64; import java.util.Enumeration;public class RsaUtils {private static Signature getSignature(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException {Signature signature;if (null == provider || provider.length() == 0) {signature = Signature.getInstance(algorithm);} else {signature = Signature.getInstance(algorithm, provider);}return signature;}/*** 验签*/public static boolean verify(byte[] message, byte[] signMessage, PublicKey publicKey, String algorithm,String provider) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {Signature signature = getSignature(algorithm, provider);signature.initVerify(publicKey);signature.update(message);return signature.verify(signMessage);}/*** 签名*/public static byte[] sign(byte[] message, PrivateKey privateKey, String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {Signature signature = getSignature(algorithm, provider);signature.initSign(privateKey);signature.update(message);return signature.sign();}/*** 公钥加密*/public static byte[] encrypt(byte[] content, PublicKey publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.ENCRYPT_MODE, publicKey);return cipher.doFinal(content);}/*** 私钥解密*/public static byte[] decrypt(byte[] content, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, privateKey);return cipher.doFinal(content);}@SuppressWarnings("java:S106")public static void main(String[] args) throws Exception {final String jwtFilePath = "P:\\git\\rsa.jws";final String storePass = "456123";final String keyPass = "212003";final String keyAlias = "oh";// 加载 JWS 文件KeyStore keyStore = KeyStore.getInstance("JKS");try (FileInputStream in = new FileInputStream(jwtFilePath)) {keyStore.load(in, storePass.toCharArray());}// 获取公钥Certificate certificate = keyStore.getCertificate(keyAlias);PublicKey publicKey = certificate.getPublicKey();String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());System.out.println("publicKey ==> " + publicKeyString);// 加载私钥, 加载私钥需要密码PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPass.toCharArray());String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());System.out.println("privateKey ==> "+ privateKeyString);System.out.println();/* 公钥加密, 私钥解密 */final String secretString = "这是密文-这是密文-这是密文";System.out.println("加密前 ==> " + secretString);byte[] secretByte = encrypt(secretString.getBytes(), publicKey);System.out.println("公钥加密 ==> " + Base64.getEncoder().encodeToString(secretByte));byte[] decodeByte = decrypt(secretByte, privateKey);System.out.println("私钥解密 ==> " + new String(decodeByte));System.out.println();/* 私钥签名加密, 公钥验证 */final String signSecretString = "密文信息";// 测试签名final byte[] sha1withRSAS = sign(signSecretString.getBytes(), privateKey, "SHA1withRSA", null);System.out.println("私钥签名: " + Base64.getEncoder().encodeToString(sha1withRSAS));// 公钥验证boolean verify = verify(signSecretString.getBytes(), sha1withRSAS, publicKey, "SHA1withRSA", null);System.out.println("公钥验证: " + verify);} }
打印输出
keyAlias: oh publicKey ==> MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlQUvw6ZEAhlxpSZWPyHuFsFiWAYgdSiM4hEVxzweZsOMian+VvsonVUzv9358Zqh+rZ/i1uQ5CeJmarfXL83LM7KNcv/O7YP98wh9N3BtCpyQwo9wcx0Ph7//T4mWnc75qCmTeOxIidi68NRisHDzg1T61u2LDyALWtb1SoOV5wt3Nts0cq+lPNxh4QTGGe2nP+mbI88VxQviCTUNneKPpBiJ7rQ2E8UYwC7WYrqLc7ZlpuYCUsZvDfsxFvBqWnCAuVMd/GxBkrOB+YbWCh7ez5TdEofU1LrgsPyrILRR7kW7NZZ2nCGfv/p8TEYqaDiyXZZzGvmDwsD/8b3YYgohwIDAQAB privateKey ==> MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCVBS/DpkQCGXGlJlY/Ie4WwWJYBiB1KIziERXHPB5mw4yJqf5W+yidVTO/3fnxmqH6tn+LW5DkJ4mZqt9cvzcszso1y/87tg/3zCH03cG0KnJDCj3BzHQ+Hv/9PiZadzvmoKZN47EiJ2Lrw1GKwcPODVPrW7YsPIAta1vVKg5XnC3c22zRyr6U83GHhBMYZ7ac/6ZsjzxXFC+IJNQ2d4o+kGInutDYTxRjALtZiuotztmWm5gJSxm8N+zEW8GpacIC5Ux38bEGSs4H5htYKHt7PlN0Sh9TUuuCw/KsgtFHuRbs1lnacIZ+/+nxMRipoOLJdlnMa+YPCwP/xvdhiCiHAgMBAAECggEAVM4O0JjeOxOfyQx4KJV2mRyUiuNxtTrOchim/CsKYhEG+ZD0XSuxgVfri1UX2JbXd4ZEL1p8qlqVxA2p724iSC2mhdcB+Uky7SIOcPuCMLW3MM+zNYbU4EVkCQpFaVZRkH38JnddZsJjWSheT0jV1X1gNKCMm8ASccaXDEhSwSgP2/0O7YbN2AuS6gP/9Ut/0zlX6KVNUQGmYZHA5Zyd2cPCsstkli/Jz9U6Cmyl4x0NH8PJo5d6Zyda/47BR5WHo37WIU6skejPYK94O4ymKnGGoT+SRFFaoMmrHgmAdX9uOoskYh+HHtwLbcQCNGo+8eiK8Lv6CQUQX4XLxeIbgQKBgQDfh7cero/xsPNGO/6vQ3vdFVwbwvb/7zjHRCS27F19Oz83B29Mj8WJbCAD+DNap16zrhby3cPPNUwvXocGbEltKrgnZWocBEpdoZRN4GgDt5lLuxUQ9ZI2DJscr9LSUJaIPD8r505xO4syHBRgshtXmF4dLgQHDDsPiaOKYxq75QKBgQCqqrdI5zn0GxFWFonTBQ4whPCr7O+dcmhCyyNZ3J/K9wF13xKTwfkDqeXPbyJLN1g0bUoLXZnBXbmJEvTndFIyaFS5qdWSXjeP2qGVYcDlNc8V3ynwk8LpnQH8e4CsDhbH1vb0F1kmvJH4c5ecrDTo5exwSEGUE58g6KSAWkFD+wKBgQCXtOVMdo8NKtpBHbDBxJxJNRj5Yn3+v54aZ54/Y/Yja1WBBJO+M4mOtgqYhxhbe2JjslCy7l3ZwMN/FrmvW0kORUMMweCdOTA7kdE0dYxCkZYB9uvaQcDE3BNeCdqckMNJnRIGuwrbAN182d/erKKv9aJSTYvAOMXQyspqvs5DHQKBgQCkMBKeP11guz2lbY9whJePFAYZ0JsBBNTLFXTP+dF8yL8N7+qGXgE7hhLByi/a3sarwUyPvJ+0CH/7IFKd7Sk6t2ZzK7F828lmSrZS6TVTDb5JU2WcvfqxFsyXYxV58R/3Z5YzY9bvzlA8DrCYGI/aU4Bw0QLN+0aGuWmw1aOeSwKBgDqbOYI6R7Lu5V5uBTD99cNfK9xtygLy3BVOQVAnyubUL1IWZMuwKKOiPAFRiSwin3OvcQVFJfndgifWRnUqWQdNktXr/xZGUa7t1yVSzuvLtZeTVkzjyeiWNLO2QqUdJsC4ZsDm9BUoK3IzFWCXrQxJU+MboiYAaopA5ZKJaRI7加密前 ==> 这是密文-这是密文-这是密文 公钥加密 ==> HYQeegMdF9Jmp9jd2mvkGjaMJ0as/gVLnfp1WjxAG62/HNfVXQWpJZZa/4aioiHYP23JYuM3a84hMw+Rb2/fkZJrSWEZBF2IbOtzPsBi2EqOKrzTdnc4FyCWNnJHwMyz2TpTsQDuU1QB3imsuUvr2xz5OQpD4KiyiechpreWqFw8Ns+1lTMGyRIyTba9bFGTBvjR2KSBVro9cUN0XaCKiqMzwjg7E7CBiO4GgIfU6fCiGAPydnYUDsAufYv/qBSKv5EuFnXuo8uWzbfxaHqzHycQ/eRXlDHIfAIlYrk/jzFz2jEtUL6D49IFstHsykWzu+mzu/EXT8P4s5FWk6ZKzg== 私钥解密 ==> 这是密文-这是密文-这是密文私钥签名: bXCQ974mmP5q3F19Pwzj3u+UHBTOWrRA057+RgWXu3EDbQa+w1pHo8S4YInI+JJFN18OPVoQOrIdoa3ipwJgTa1rNuDsCOE5FfPa2yqiHDpD/t6q11L5cGC91+Y5c46NSBrXUTjpta4t+0Y6Xld62vVrAHq7XdrieLkbzn2+gQGPbkxiLxkvHoJDTl0S5EB86qcMlezRcxKWvzGPmugK83YMMUcBVi12YHB583EJeQ2QKXIIJqOLE8FL45zudhqfVOcgXXifRs0T5mwN0hBEHgi4cc2jbbViZwodf81NM0USaolNPVcB1WbWcmCn/MUS2dp8vTDAN0cmOMVD17SMxw== 公钥验证: true
附: 单点登录: Spring security
+ auth2
+ JWT
+ RSA
相关局部配置
这仅仅是部分配置: 针对 Jwt 转换器
的配置
package cn.hydroxyl.sso.auth2.config;import cn.hydroxyl.sso.auth2.config.comp.JwtTokenEnhancer;
import cn.hydroxyl.sso.auth2.service.UserServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {private final UserServiceImpl userDetailsService;private final AuthenticationManager authenticationManager;private final JwtTokenEnhancer jwtTokenEnhancer;/*** 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。** 1. 密码模式下配置认证管理器 AuthenticationManager 以及 jwt* 2. 设置 AccessToken的存储介质tokenStore, 默认使用内存当做存储介质。*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {List<TokenEnhancer> delegates = new ArrayList<>();delegates.add(jwtTokenEnhancer);delegates.add(accessTokenConverter());// 密码模式下使用 authenticationManager 管理器endpoints.authenticationManager(authenticationManager)// 配置加载用户信息的服务.userDetailsService(userDetailsService).accessTokenConverter(accessTokenConverter());}/*** Jwt 转换器*/@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();jwtAccessTokenConverter.setKeyPair(keyPair());return jwtAccessTokenConverter;}@Beanpublic KeyPair keyPair() {// rsa 密钥文件, 以及对应的 storePass, keyPass, aliasfinal ClassPathResource rsaJksRes = new ClassPathResource("rsa.jks");final String storePass = "456123";final String keyPass = "212003";final String alias = "oh";return new KeyStoreKeyFactory(rsaJksRes, storePass.toCharArray()).getKeyPair(alias, keyPass.toCharArray());}
}
参考自:
- https://www.cnblogs.com/wrc-blog/p/14256071.html
- https://www.jianshu.com/p/e34a579c63a0
针对JWT简介与原理,代码实例,以及oauth2+JWT+RSA的集成配置相关推荐
- java cas登陆实例_Java CAS基本实现原理代码实例解析
一.前言 了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的简称.它有核心就是CAS与AQS.CAS是java.util.concurrent.a ...
- jwt 例子 java_spring boot 入门之security oauth2 jwt完美整合例子-java编程
一.本文简介 本文主要讲解Java编程中spring boot框架+spring security框架+spring security oauth2框架整合的例子,并且oauth2整合使用jwt方式存 ...
- 「springcloud 2021 系列」Spring Cloud Gateway + OAuth2 + JWT 实现统一认证与鉴权
通过认证服务进行统一认证,然后通过网关来统一校验认证和鉴权. 将采用 Nacos 作为注册中心,Gateway 作为网关,使用 nimbus-jose-jwt JWT 库操作 JWT 令牌 理论介绍 ...
- 【学习笔记】Eureka服务治理代码实例、相关配置和原理机制详解
文章目录 代码示例 启动一个服务注册中心 注册服务提供者 高可用注册中心 服务的发现与消费 Eureka的一些配置 服务注册类配置 服务实例类配置 实例名配置 端点配置 Eureka服务治理基础架构原 ...
- AIGC之GPT-4:GPT-4的简介(核心原理/意义/亮点/技术点/缺点/使用建议)、使用方法、案例应用(计算能力/代码能力/看图能力等)之详细攻略
AIGC之GPT-4:GPT-4的简介(核心原理/意义/亮点/技术点/缺点/使用建议).使用方法.案例应用(计算能力/代码能力/看图能力等)之详细攻略 解读:在2022年11月横空出世的ChatGPT ...
- 【编程实践】Raft 算法的原理 go代码实例
文章目录 Raft 算法的原理 & go代码实例 Raft 算法的原理 使用 Go 语言实现的简单 Raft 算法示例 Raft 算法的原理 & go代码实例 Raft 算法的原理 R ...
- c语言滚动字幕的原理编程,c#中通过Graphics.DrawString实现滚动字幕的原理和代码实例...
c#中通过Graphics.DrawString实现滚动字幕的原理和代码实例 在c#中其实滚动屏幕的实现很简单,只需要用到Graphics.DrawString方法. Graphics.DrawStr ...
- python遍历queryset_Django QuerySet查询集原理及代码实例
一 概念 Django的ORM中存在查询集的概念. 查询集,也称查询结果集.QuerySet,表示从数据库中获取的对象集合. 当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表): a ...
- TFRecord简介,原理分析,代码实现?
TFRecord简介,原理分析,代码实现? 在利用深度学习算法搭建完成网络之后,我们要对网络进行训练,要训练网络就要有训练数据,通常我们会直接对硬盘上存放数据进行操作,来fetch到网络中.这样直接从 ...
最新文章
- CG CTF CRYPTO Keyboard
- 虚拟光驱的开发者斟酌了很久
- Altium AD20焊盘样式、热焊盘与反焊盘与直接连接
- Oracle 痛裁程序员,阿里云坐收渔翁利?
- webdriver原理(自己做个记录)
- 2020年浙江省土地利用数据(矢量)
- 海康威视摄像头+OpenCV+VS2017 图像处理小结(一)
- 控制系统设计专题(一)——PID控制算法
- Vmware安装Vmware Tools工具
- sql 2008 R2 备份和还原
- 双拼输入法的原理及上手方法
- Linux_29_Linux-Vsftpd
- java 下载文件的文件名乱码_JAVA 文件下载时的文件名乱码解决
- 2020-12-22 ACM集训一(二维数组与结构体)
- Python运维开发(CMDB资产管理系统)——环境部署(下)
- int型整数的最小值和最大值是多少(精确值)
- 无锡中软国际有限公司笔试题(Java)
- 【算法和数据结构】模拟和暴力
- 解决ROS中运行launch文件报错ERROR: cannot launch node of type[xxx/xxx]:xxx的问题办法最全汇总
- 湖北省制造业高质量发展专项奖励申报条件,2022年揭榜挂帅项目指南
热门文章
- 基于迁移学习的PyTorch图像分类
- function函数的各种写法
- 应届生找嵌入式工作难吗?
- 5年1万亿:揭秘中行供应链金融关键词
- Linux【实操篇】—— 日志管理
- 你好,请开下门,查水表|宅客周刊
- 帆软报表分页预览打印,如果列数过多,打印时会将多余的列放到第二页来打印,现在需要把所有的列都放在一页来打印。并且填满整个区域
- Redis学习笔记~Redis事务机制与Lind.DDD.Repositories.Redis事务机制的实现
- STM32系统滴答_及不可不知的延时技巧 - (下)
- win7家庭版和旗舰版区别_WIN7_64位系统安装 MicroWIN_SP9后没有PC-PPI通讯协议怎么处理?...