200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 从0搭建公司SpringCloud Alibaba 微服務鉴权中心服务(详细教程)

从0搭建公司SpringCloud Alibaba 微服務鉴权中心服务(详细教程)

时间:2022-06-20 19:07:00

相关推荐

从0搭建公司SpringCloud Alibaba 微服務鉴权中心服务(详细教程)

程序员的成长之路

互联网/程序员/技术/资料共享

关注

阅读本文大概需要 15分钟。

来自:/doomwatcher/article/details/121743887

鉴权中心服务

认识JWT

json web token 是一个开放的标准 ,它定义了一个种紧凑的,自包含的方式,用于作为json对象在各方之间安全的传输信息

服务器鉴权完成之后 会生成 json 对象 发送给客户端,之后客户端和服务端传输数据都需要带上这个对象,服务器完全通过这个json对象认定客户端身份,为了防止篡改数据,服务端在生成的时候都会加上签名(加密的意思),服务器不保存session数据也就是无状态,更适合实现扩展

那些环境可以考虑使用jwt呢?用户授权 ,信息交换

JWT组成部分

Header :头部信息

Header 由两部分组成(Token类型,加密算法的名称),并且使用的是base64的编码

Payload:我们想要传递的数据

Payload KV形式的诗数据 ,这里就是我们想要传递的信息(授权的话就是Token信息)

Signature :签名

Signature 为了得到签名 首先我们得有编码过的Header 编码过的payload 和一个密钥。签名用的算法就是header中指定的那个,之后就会对他们签名

我们需要一个签名公式

HMACSHA245(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

产生一个签名,返回一个字符串,返回给客户端,之后客户端每次访问都要带上这个字符串,进行鉴权

JWT使用.号来连接HHH.PPPP.SSSS

授权,鉴权设计

这里我们先不考虑 gateway 网关,后续会搭建,我们的重点放在中间和右边部分

鉴权部分,我们独立实现公共的工具类,为什么?以下三点

JWT本质上是通过算法算出的加密字符串,也可以通过算法反向解析出来,他不依赖任何的框架,所以这个功能有可以单独提取出来的前提

我们的电商系统包含多个微服务,很显然我们每个服务都需要鉴权,于是我们把这个方法提取出来,方便复用

高性能鉴权,为什么不在授权中心做鉴权,首先他回头过http请求等一系列操作,我们在本地只用java的话 少去了很多步骤,性能得到倍数的增长

授权编码实现

我们创建新的一个服务来编写我们的鉴权中心

e-commerce-authority-center

导入相关的依赖

<dependencies><!--springcloudalibabanacosdiscovery依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2.2.3.RELEASE</version></dependency><!--JavaPersistenceAPI,ORM规范--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!--MySQL驱动,注意,这个需要与MySQL版本对应--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.12</version><scope>runtime</scope></dependency><dependency><groupId>com.hyc.ecommerce</groupId><artifactId>e-commerce-mvc-config</artifactId><version>1.0-SNAPSHOT</version></dependency><!--zipkin=spring-cloud-starter-sleuth+spring-cloud-sleuth-zipkin--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zipkin</artifactId></dependency><dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId><version>2.5.0.RELEASE</version></dependency><!--screw生成数据库文档--><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.30</version></dependency><dependency><groupId>cn.smallbun.screw</groupId><artifactId>screw-core</artifactId><version>1.0.3</version></dependency></dependencies>

导入好依赖之后我们 编写对应的配置,如注册到naocs 加入adminserver的监管,配置数据源等 这里我们使用jpa 来做orm

配置编写

server:port:7000servlet:context-path:/ecommerce-authority-centerspring:application:name:e-commerce-authority-centercloud:nacos:discovery:enabled:true#如果不想使用Nacos进行服务注册和发现,设置为false即可server-addr:127.0.0.1:8848#Nacos服务器地址#server-addr:127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850#Nacos服务器地址namespace:1bc13fd5-843b-4ac0-aa55-695c25bc0ac6metadata:management:context-path:${server.servlet.context-path}/actuatorjpa:show-sql:truehibernate:ddl-auto:noneproperties:hibernate.show_sql:truehibernate.format_sql:trueopen-in-view:falsedatasource:#数据源url:jdbc:mysql://127.0.0.1:3306/imooc_e_commerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTCusername:rootpassword:roottype:com.zaxxer.hikari.HikariDataSourcedriver-class-name:com.mysql.cj.jdbc.Driver#连接池hikari:maximum-pool-size:8minimum-idle:4idle-timeout:30000connection-timeout:30000max-lifetime:45000auto-commit:truepool-name:ImoocEcommerceHikariCPkafka:bootstrap-servers:127.0.0.1:9092producer:retries:3consumer:auto-offset-reset:latestzipkin:sender:type:kafka#默认是webbase-url:http://127.0.0.1:9411/#暴露端点management:endpoints:web:exposure:include:'*'endpoint:health:show-details:always

配置完成之后,编写主启动类@EnableJpaAuditing因为我们用到 自动加入创建时间和修改时间,所以我们需要打开 jpa的自动审计功能,不然会报错

@EnableJpaAuditing//允许jpa的自动审计@SpringBootApplication@EnableDiscoveryClientpublicclassAuthorityApplication{publicstaticvoidmain(String[]args){SpringApplication.run(AuthorityApplication.class,args);}}

test包下就测试环境是否正确

/***授权中心测试入口*验证授权中心环境可用性*/@SpringBootTest@RunWith(SpringRunner.class)publicclassAuthorityCenterApplicationTest{@TestpublicvoidconetextLoad(){}}

环境ok之后

我们去测试 数据库操作是否可用

编写实体类ecommerceUser

/**用户表实体类定义**/@Entity@EntityListeners(AuditingEntityListener.class)@Table(name="t_ecommerce_user")@Data@NoArgsConstructor@AllArgsConstructorpublicclassEcommerceUser{/*自增组件*/@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privatelongid;/*用户名*/@Column(name="username",nullable=false)privateStringusername;/*MD5密码*/@Column(name="password",nullable=false)privateStringpassword;/*额外的信息json字符串存储*/@Column(name="extra_info",nullable=false)privateStringextraInfo;/*自动加入创建时间需要主启动类的注解*/@CreatedDate@Column(name="create_time",nullable=false)privateDatecreateTime;/*自动加入更新时间需要主启动类的注解*/@CreatedDate@Column(name="update_time",nullable=false)privateDateupdateTime;}

有了实体类我们需要有数据操作的实现 于是编写Dao 接口

其实当我们创建接口的时候jpa就已经有了对应的基础增删改查的方法

这里我们实现两个自定义查询方法

/***EcommerceUserDao接口定义*/publicinterfaceEcommerceUserDaoextendsJpaRepository<EcommerceUser,Long>{/**根据用户名查询EcommerceUser对象*等于select*formt_ecommerce_userwhereusername=?**/EcommerceUserfindByUsername(Stringname);/**根据用户名查询EcommerceUser对象*等于select*formt_ecommerce_userwhereusername=?andpassword=?**/EcommerceUserfindByUsernameAndPassword(Stringname,Stringpassword);}

之后创建 test service

/***@author:冷环渊*@date:/12/4*@context:EcommerceUser相关测试*@params:null*@return:*@return:null*/@SpringBootTest@RunWith(SpringRunner.class)@Slf4jpublicclassEcommerUserTest{@AutowiredEcommerceUserDaoecommerceUserDao;/*测试新增一个用户数据*/@TestpublicvoidcreateUserRecord(){EcommerceUserecommerceUser=newEcommerceUser();//设置要插入的信息ecommerceUser.setUsername("hyc@");ecommerceUser.setPassword(MD5.create().digestHex("123456"));ecommerceUser.setExtraInfo("{}");//日志打印返回结果log.info("serveruser:[{}]",JSON.toJSON(ecommerceUserDao.save(ecommerceUser)));}/*测试我们编写的自定义方法查询刚才创建的新角色*/@TestpublicvoidSelectUserInfo(){Stringusername="hyc@";log.info("selectuserinof:[{}]",JSON.toJSON(ecommerceUserDao.findByUsername(username)));}}

测试相关的 方法 新增用户啊 或者是 按条件查询用户 ,测试均通过

生成RSA256的公钥 和 私钥 非对称加密算法

他通过 私钥加密 公钥解密来完成验证,目前很多的鉴权 都是 JWTRSA256的算法来加密鉴权的,如果了解不多,就是用RSA256就可以了

编码

编写生成公钥密钥的测试类,创建 一些我们常用的VO对象 用来储存我们常用的一些变量,比如用户信息,公钥,密钥,一些常用的属性 放进 VO的模型里

@Slf4j@SpringBootTest@RunWith(SpringRunner.class)/****@author:冷环渊*@date:/12/5*@context:RSA非对称加密算法*@params:null*@return:*@return:null*/publicclassRSATest{@TestpublicvoidgenerateKeyBytes()throwsException{/*获取到RSA算法实例*/KeyPairGeneratorkeyPairGenerator=KeyPairGenerator.getInstance("RSA");/*这里最小是2048低于的话是会报错的*/keyPairGenerator.initialize(2048);/**生成公钥对**/KeyPairkeyPair=keyPairGenerator.generateKeyPair();/*获取公钥和私钥对象*/RSAPublicKeypublicKey=(RSAPublicKey)keyPair.getPublic();RSAPrivateKeyprivateKey=(RSAPrivateKey)keyPair.getPrivate();log.info("privatekey:[{}]",Base64.encode(privateKey.getEncoded()));log.info("publickey:[{}]",Base64.encode(publicKey.getEncoded()));}}

创建VO对象保存 我们常用且不会变化的值和对象

存储私钥 应为是私钥 所以只对鉴权中心 暴露 于是我们在鉴权服务中创建Constant包创建这个AuthotityConstant类保存信息

/***@author:冷环渊*@date:/12/5*@context:鉴权的常量*@params:null*@return:*@return:null*/publicclassAuthorCanstant{/*私钥只暴露给鉴权中心不暴露给任何的其他服务*/publicstaticfinalStringPRIVATE_KEY="MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBA"+"QCMXrQCudalKHJlH16YHr9mI5/xyYnkp5u2gAbMFf2xAHAyykYmixJP3CqG2a8tUwiJjjTIJXP+79Jzgjgg"+"VbBaTakrvjeFXz9HNP1D4XD6Li+sRVjnN1iBUwIFRxiFN2EOJflA9bqeQLAge/LgAu06y3jdLLleJF7yDRuMH"+"YedqPl9AJa5RdJmt0OgCoVOqacB7oGkFCFISm0Cwjfgq06nyiiULGZNVt8uhDxZAE4Pi2lmf3yggXCBH9AtU/2"+"XdyxU9caQJOAbYGxd/mART/NivBjSqo60wcBnktI+booUbDKRBbWRxvfYqKWEwPOwxlJUB3l3pcLZm866Xl3qtVM"+"XAgMBAAECggEADCGjLRkik+OK/3JWmo8Nu6YYjKz+XeSecIdgDwNXiZSgHcOdjHc4fe5pPn5RxXkHo9vGdAXIoJ/Z"+"cGIwt5qwQx2zITSvV7eDoIPT36n8OaMEO79Cj7kYzRR/eDVMyTagDLj7ccHK/yJYFnaf5vxZxFsRdwwGeTxreD"+"/pwZJLxjRSz1W57v5yUJNPPimNB229EogNYHIhQ8+Z7OGiilbtBIL9r6lqlz2hUAVBzXl4kOXFVI+vEodLuV2"+"rtQXXrpO1+AgH5lZJ7ahShKbqHt/Q6uJSTKAhbsfv/iadcPjmYp2F7nnYBLf66Jln6AWUwnXrJ7XETOf/+Qcib"+"q/5m6RjAQKBgQDruxn+kaDr5uYQMVSHog+CBRBJghJ4JklhY7ZDYJ2wN2KNHOd3mW/wUVDihVIyRFniIzsWU"+"0lnI+4OLqNLAZOBaQB5VrjyH4fxn5b26t0xLO1d5EWcOYI8ZRhwWDWaZipe2dUMeqVVMYFeDdTdNsyGrf8x"+"L+OVyRDiH4s4pBIs7QKBgQCYcIVFgDbrmwsP7lA9/dU9kClutY3gjEUgB2IJp2Y8S4Xhfi4NC8GqRQoMUyuqg"+"vPHKEiTCa1EojGHS/+r4JVcSg9Wsv64SpGZ+gANxRhfYFPrbkjU4YOMaZeCGUfKR2QnD20c3I4gdQ9kU5nK52n+Y"+"JEkAFUejg1Mhb6Fp6HDkwKBgAHYYBa3CxxtnUVpLXE2Woq5AWyh4QUhv5dMkYOrgPB9Ln9OR52PDOpDqK9tP"+"bx4/n8fqXm+QyfUhyuDP/H5XC86JC/O9vmmN4kzp5ndMsgMwvrmK4lShet1GyDd/+VqgVBmwh0r5JlrHske"+"sJjesfEn8YRwDIcCoOg0OQHDfwTtAoGAQfE61YvXNihFqsiOkaKCYjVAlxGWpDJJnMdU05REl4ScD6WDy"+"kTxq/RdmmNIGmS3i8mTS3f+Khh3kG2B1ho6wkePRxP7OEGZpqAM8ef22RtUch2tB9neDBmJXtAMzCYB3xu/O"+"aL3IHdDB0Va2/krUsz3PDmgmK0ed6HLfwm64l0CgYB+iGkMAQEwqYmcCEXKK825Q9y/u8PE9y8uaMGfsZQzDo6v"+"V5v+reOhmZRrk5BnX+pgztbE28sS6c2vYR0RYoR90aD2GXungCPXWEMDQudHFxvSsNTCYkDynjTSlnzu9aDcfqw1"+"UIzHog2zCquSro7tnbOMsvV5UdsLBq+WNQGgAw==";/*默认的token超时时间,一天*/publicstaticfinalIntegerDEFAULT_EXPIRE_DAY=1;}

之后是创建一些公共常用的VO模型 e-commerce-common

保存 公钥到公用包 以后我们的服务 需要做授权都需要使用到

/***@author:冷环渊*@date:/12/5*@context:通用模块的常量定义*@params:null*@return:*@return:null*/publicclassCommonCanstant{/*RSA公钥*/publicstaticfinalStringPUBLIC_KEY="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjF60ArnWpShyZ"+"R9emB6/ZiOf8cmJ5KebtoAGzBX9sQBwMspGJosST9wqhtmvLVMIiY40yCVz/u/Sc4I4IFWwWk2pK743hV8/RzT9Q+F"+"w+i4vrEVY5zdYgVMCBUcYhTdhDiX5QPW6nkCwIHvy4ALtOst43Sy5XiRe8g0bjB2Hnaj5fQCWuUXSZrdDoAqFTqmnA"+"e6BpBQhSEptAsI34KtOp8oolCxmTVbfLoQ8WQBOD4tpZn98oIFwgR/QLVP9l3csVPXGkCTgG2BsXf5gEU/zYrwY0qqO"+"tMHAZ5LSPm6KFGwykQW1kcb32KilhMDzsMZSVAd5d6XC2ZvOul5d6rVTFwIDAQAB";/*JWT中存储用户信息到key*/publicstaticfinalStringJWT_USER_INFO_KEY="e-commerce-user";/*授权中心的service-id*/publicstaticfinalStringAUTHORITY_CENTER_SERVICE_ID="e-commerce-authity-center";}

用户信息的常用VO对象

JwtToken

/***@author:冷环渊*@date:/12/5*@context:授权中心鉴权之后给客户端的token*@params:null*@return:*@return:null*/@Data@NoArgsConstructor@AllArgsConstructorpublicclassJwtToken{/*JWT*/privateStringtoken;}

LoginUserinfo

@Data@NoArgsConstructor@AllArgsConstructorpublicclassLoginUserinfo{/*用户id*/privateLongid;/*用户名*/privateStringusername;}

UsernameAndPassword

/***@author:冷环渊*@date:/12/5*@context:用户名和密码*@params:null*@return:*@return:null*/@Data@AllArgsConstructor@NoArgsConstructorpublicclassUsernameAndPassword{/*用户名*/privateStringusername;/*密码*/privateStringpassword;}

授权服务编写

首先创建一个 接口 IJWTService

定义我们需要实现的授权方法

/***@author:冷环渊*@date:/12/5*@context:JWT相关服务接口定义*@params:null*@return:*@return:null*/publicinterfaceIJWTService{/**生成token使用默认的超时时间**/StringgenerateToken(Stringusername,Stringpassword)throwsException;/**生成JWTToken可以设置超时时间单位是天**/StringgenerateToken(Stringusername,Stringpassword,IntegerexpireTime)throwsException;/**注册用户并且生成token返回**/StringregisterUserAndGenerateToken(UsernameAndPasswordusernameAndPassword)throwsException;}

授权方法实现类

这里我们有三个方法实现

默认超时时间的 生成 token

自定义超时时间的设置生成token

注册新用户并且生成的token返回

JWT对象生成细节:

1) 我们需要设置需要传递的对象

2)我们需要设置一个不重复的 id

3)我们需要设置超时时间

4)设置我们的加密签名

5)完成设置返回字符串对象

Jwts.builder()//这里claim其实就是jwt的payload对象-->KV.claim(CommonCanstant.JWT_USER_INFO_KEY,JSON.toJSONString(loginUserinfo))//jwtid表示是jwt的id.setId(UUID.randomUUID().toString())//jwt的过期时间.setExpiration(expireDate)//这里是设置加密的私钥和加密类型.signWith(getPrivateKey(),SignatureAlgorithm.RS256)//生成jwt信息返回的是一个字符串类型.compact();}

完整代码

@Service@Slf4j@Transactional(rollbackFor=Exception.class)publicclassIJWTServiceIpmlimplementsIJWTService{@AutowiredprivateEcommerceUserDaoecommerceUserDao;@OverridepublicStringgenerateToken(Stringusername,Stringpassword)throwsException{returngenerateToken(username,password,0);}@OverridepublicStringgenerateToken(Stringusername,Stringpassword,IntegerexpireTime)throwsException{//首先需要验证用户是否通过授权校验,即输入的用户名和密码能否寻找到匹配数据表的记录EcommerceUserecommerceUser=ecommerceUserDao.findByUsernameAndPassword(username,password);if(ecommerceUser==null){log.error("can not find user:[{}],[{}]",username,password);returnnull;}//Token中塞入对象,即JWT中储存的对象,后端拿到这些信息就可以知道那个用户在操作LoginUserinfologinUserinfo=newLoginUserinfo(ecommerceUser.getId(),ecommerceUser.getUsername());if(expireTime<=0){expireTime=AuthorCanstant.DEFAULT_EXPIRE_DAY;}//计算超时时间ZonedDateTimezdt=LocalDate.now().plus(expireTime,ChronoUnit.DAYS).atStartOfDay(ZoneId.systemDefault());DateexpireDate=Date.from(zdt.toInstant());returnJwts.builder()//这里claim其实就是jwt的payload对象-->KV.claim(CommonCanstant.JWT_USER_INFO_KEY,JSON.toJSONString(loginUserinfo))//jwtid表示是jwt的id.setId(UUID.randomUUID().toString())//jwt的过期时间.setExpiration(expireDate)//这里是设置加密的私钥和加密类型.signWith(getPrivateKey(),SignatureAlgorithm.RS256)//生成jwt信息返回的是一个字符串类型.compact();}@OverridepublicStringregisterUserAndGenerateToken(UsernameAndPasswordusernameAndPassword)throwsException{//先去校验用户名是否存在如果存在不能重复注册EcommerceUseroldUser=ecommerceUserDao.findByUsername(usernameAndPassword.getUsername());if(null!=oldUser){log.error("usernameisregistered:[{}]",oldUser.getUsername());returnnull;}EcommerceUserecommerceUser=newEcommerceUser();ecommerceUser.setUsername(usernameAndPassword.getUsername());ecommerceUser.setPassword(usernameAndPassword.getPassword());//MD5编码以后ecommerceUser.setExtraInfo("{}");//注册一个新用户写到一个记录表中ecommerceUser=ecommerceUserDao.save(ecommerceUser);log.info("regiterusersuccess:[{}],[{}]",ecommerceUser.getUsername());//生成token并且返回returngenerateToken(ecommerceUser.getUsername(),ecommerceUser.getPassword());}/**根据本地储存的私钥获取到PrivateKey对象**/privatePrivateKeygetPrivateKey()throwsException{//使用给定的编码密钥创建一个新的PKCS8EncodedKeySpec。PKCS8EncodedKeySpecpriPKCS8=newPKCS8EncodedKeySpec(newBASE64Decoder().decodeBuffer(AuthorCanstant.PRIVATE_KEY));//设置生成新密钥的工厂加密方式KeyFactorykeyFactory=KeyFactory.getInstance("RSA");//返回生成好的密钥returnkeyFactory.generatePrivate(priPKCS8);}}

之后我们的授权都会使用到以上的方法

Controller

我们需要给注册用户和生成token 一个程序的入口

就是我们的 AuthorityController,这里可以用到我们之前使用的注解@IgnoreResponseAdvice我们为啥那么不让他封装呢,我们需要验证,单纯的 JwtToken对象就可以了,不需要封装和转化

@Slf4j@RestController@RequestMapping("/authority")publicclassAuthorityConroller{privatefinalIJWTServiceljwtService;publicAuthorityConroller(IJWTServiceljwtService){this.ljwtService=ljwtService;}/**从授权中心获取token(其实就是登陆功能)且返回信息中没有统一响应的包装**/@IgnoreResponseAdvice@PostMapping("/token")publicJwtTokentoken(@RequestBodyUsernameAndPasswordusernameAndPassword)throwsException{//通常日志里不会答打印用户的信息防止泄露,我们这本身就是一个授权服务器,本身就不对外开放,所以我们可以打印用户信息到日志方便查看log.info("requesttogettokenwithparam:[{}]",JSON.toJSONString(usernameAndPassword));returnnewJwtToken(ljwtService.generateToken(usernameAndPassword.getUsername(),usernameAndPassword.getPassword()));}/*注册用户并且返回注册当前用户的token就是通过授权中心常见用户*/@IgnoreResponseAdvice@PostMapping("/register")publicJwtTokenregister(@RequestBodyUsernameAndPasswordusernameAndPassword)throwsException{log.info("registeruserwithparam:[{}]",JSON.toJSONString(usernameAndPassword));returnnewJwtToken(ljwtService.registerUserAndGenerateToken(usernameAndPassword));}}

鉴权编码实现

这里我们打鉴权 放到公共模块里 为什么呢,这里我们不止是鉴权中心还有其他的服务也要用到鉴权服务,秉着封装的思想,我们提取公共的方法放到 Common里面

创建JWT Token解析类TokenParseUtil

/***@author:冷环渊*@date:/12/5*@context:JWTToken解析工具类*@params:null*@return:*@return:null*/publicclassTokenParseUtil{publicstaticLoginUserinfoparseUserInfoFromToken(Stringtoken)throwsException{if(null==token){returnnull;}Jws<Claims>claimsJws=parseToken(token,getPublicKey());Claimsbody=claimsJws.getBody();//如果Token已经过期返回nullif(body.getExpiration().before(Calendar.getInstance().getTime())){returnnull;}//返回Token中保存的用户信息returnJSON.parseObject(body.get(CommonCanstant.JWT_USER_INFO_KEY).toString(),LoginUserinfo.class);}/**通过公钥去解析JWTToken**/privatestaticJws<Claims>parseToken(Stringtoken,PublicKeypublicKey){//用设置签名公钥,解析claims信息tokenreturnJwts.parser().setSigningKey(publicKey).parseClaimsJws(token);}/**根据本地存储的公钥获取到getPublicKey**/publicstaticPublicKeygetPublicKey()throwsException{//解码器我们设置解码器将公钥放进去X509EncodedKeySpeckeySpec=newX509EncodedKeySpec(newBASE64Decoder().decodeBuffer(CommonCanstant.PUBLIC_KEY));//创建RSA实例通过示例生成公钥对象returnKeyFactory.getInstance("RSA").generatePublic(keySpec);}}

这里是涉及到一个问题 ,token要是传输的不是jwt token对象,会跑出异常,没有兜底,

其实这里这问题其实也不成立,应为你没有传入token对象,我们这里抛出异常是正确的,也不会影响其他服务,之后搭配sentinel和豪猪哥 可以实现异常重启等等,这里我们就先不编写兜底方法,以解析jwt token为主。

验证鉴权授权

我们写一个 test 类来测试 授权和鉴权拿到对象,是否有效

/***@author:冷环渊*@date:/12/5*@context:JWT相关测试类*@params:null*@return:*@return:null*/@Slf4j@SpringBootTest@RunWith(SpringRunner.class)publicclassJWTServiceTest{@AutowiredprivateIJWTServiceijwtService;@TestpublicvoidtestGenerateAndParseToken()throwsException{StringjwtToken=ijwtService.generateToken("hyc@","e10adc3949ba59abbe56e057f20f883e");log.info("jwttokenis:[{}]",jwtToken);LoginUserinfouserinfo=TokenParseUtil.parseUserInfoFromToken(jwtToken);log.info("userinfobyjwtprasetoken:[{}]",JSON.toJSONString(userinfo));}}

启动测试查看结果

eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjExLFwidXNlcm5hbWVcIjpcImh5Y0BxcS5jb21cIn0iLCJqdGkiOiIzNDgwNjdjMi00MTBlLTQ3MjItYmM3ZS02NWQyYmNmYTRkN2MiLCJleHAiOjE2Mzg3MjAwMDB9.ZbFl81MkIipJSULZLf4F2X2Fb0q1TwhHIMT7nyZsZVwUxXyZnK54RlzoGM_b-kMUdKO_Tab-qEeOT6Jn--FiKmbOziWXiBx3a-k5ipthMJx0Fez-X8Acty-Pg7zukNalugiLxGb5ophQoVQWRTDmv2hytGHqiV71HVyErznkJa36QQr6QsjXqlJleo3BBt-6BFzdTFPLUmdTEJ4XsmZBa_acUDGBhY0_tU2gYtKBWhwvMCknuyCcV-_GVI5EvgMIKRpeFSZrWfTsDG2y1MFcyzjKE6jnzek-YwT3XkzQ8eGzUbiOlaU_Zx5OJah-UtrKwqlAw9WbO71pNgEBefdsYw

这是封装好的 JWT Token 这里我们可以看到三个点分别分割 了 header和payload以及签名,和我们之前讲的 结构一模一样,

userinfobyjwtprasetoken:[{"id":11,"username":"hyc@"}]

获取到的我们放在jwt 里面需要传递的对象

验证对外提供的接口是否好用

这里我们编写 http脚本来测试对外题提供的接口是否有用

Token 方法

###获取Token--登录功能实现POSThttp://127.0.0.1:7000/ecommerce-authority-center/authority/tokenContent-Type:application/json{"username":"hyc@","password":"e10adc3949ba59abbe56e057f20f883e"}

POSThttp://127.0.0.1:7000/ecommerce-authority-center/authority/tokenHTTP/1.1200Content-Type:application/jsonTransfer-Encoding:chunkedDate:Sun,05Dec15:35:52GMTKeep-Alive:timeout=60Connection:keep-alive{"token":"eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjExLFwidXNlcm5hbWVcIjpcImh5Y0BxcS5jb21cIn0iLCJqdGkiOiIxNDU1M2FjZi1lZmE5LTQ4OTgtOTliYS1hNzA4NWI4MjU4MzAiLCJleHAiOjE2Mzg3MjAwMDB9.AlOpo6uf97R20ZLojXeun-3MK8DpSYlWxEygvDrtQeWaM9R0iKx-iW1VXnK6WoEntvqPxIrmPA7khjl3dXPa8kQHtdq-LVO7BDuZZDiQyZ64ZS7A9jWZr5JReSWBUSR1YUnsOvBRMkx4JVcAF3_W7nHwd722FFzOZRCr72hLHQIKpsugKtqjMEtaiEW0vcqphCYRJTAO_rQx1Lb1eVVg_Ufur0qSlKkV5dSJ0x3x9mc9UZRckwN0rrP7wQxZcrxJvKTfX7CkRRSO-CxZbG4WLokSaMtaGBMWU-7KGq7HSCZ0yuOgbbLdouHncsp6VD2tNLFdWSdJ_whCIbZxfX8R7w"}

获取 token 成功

这里他没有被响应包裹,证明我们之前的选择屏蔽注解也生效了,很符合我们的预期

验证如果记录数据表没有是否会返回null

###获取Token--登录功能实现POSThttp://127.0.0.1:7000/ecommerce-authority-center/authority/tokenContent-Type:application/json###随便写的id{"username":"hyc1111@","password":"e10adc3949ba59abbe56e057f20f883e"}

返回结果 也符合我们预期 是 null

POSThttp://127.0.0.1:7000/ecommerce-authority-center/authority/tokenHTTP/1.1200Content-Type:application/jsonTransfer-Encoding:chunkedDate:Sun,05Dec15:40:44GMTKeep-Alive:timeout=60Connection:keep-alive{"token":null}

register

###注册用户并返回Token--注册功能实现POSThttp://127.0.0.1:7000/ecommerce-authority-center/authority/registerContent-Type:application/json{"username":"hyc@","password":"e10adc3949ba59abbe56e057f20f883e"}

这个用户之前是注册过的,我们来看一下是否会返回我们预期的处理

POSThttp://127.0.0.1:7000/ecommerce-authority-center/authority/registerHTTP/1.1200Content-Type:application/jsonTransfer-Encoding:chunkedDate:Sun,05Dec15:42:00GMTKeep-Alive:timeout=60Connection:keep-alive{"token":null}

现在我们去注册一个新的用户

###注册用户并返回Token--注册功能实现POSThttp://127.0.0.1:7000/ecommerce-authority-center/authority/registerContent-Type:application/json{"username":"hyc11@","password":"e10adc3949ba59abbe56e057f20f883e"}

符合预期结果,创建了我们预期的对象,这个时候我们去看一下数据表

POSThttp://127.0.0.1:7000/ecommerce-authority-center/authority/registerHTTP/1.1200Content-Type:application/jsonTransfer-Encoding:chunkedDate:Sun,05Dec15:42:57GMTKeep-Alive:timeout=60Connection:keep-alive{"token":"eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEyLFwidXNlcm5hbWVcIjpcImh5YzExQHFxLmNvbVwifSIsImp0aSI6IjMxNDc0NmIwLTMyOGYtNDZkNS05ZTIwLTg3YjI0OWY1ZjZkOCIsImV4cCI6MTYzODcyMDAwMH0.MKxk-Q4BG5kaYFAsLiy13trtk_gDFmCKORpdE4EAwgSVecXFQcYfT1VvqSAKvoQLFsSlQAxOR5elV8CFOoKwAomwqdyyghZp63NKJ2smRbg3Y-4jWBzFVsUgcjOY2fwh7oNTdHEsWmLBYAh5r0hm_MysZsUEsE-cwb3sw8NSMk1OZp0J6tcRras7V1Uw5xXH8OnCoq2cUfdynJMHS29EzJT1TFPb8unVQ_A1RWodsHdK3n1Bl4wFbJjMtnHx7vzOeAUSNJx1XpAGdo0xYHK6HBpS9E1KBS3x1AnYFONM0DKd4-_QxMkBW1kkg2uWrRpf3GYZF20FKxXgmBAPHGZhew"}

对象生成,功能验证一切正常

鉴权服务中心总结

对比基于Token与基于服务器的身份认证

传统:

最为传统的做法,客户端储存 cookie 一般是 Session id 服务器存储 Session

Session 是每次用户认证通过以后 ,服务器需要创建一条记录保存用户信息,通常是在内存中(也可以放在redis中),随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大

不同域名之前切换的时候,请求可能会被禁止,即跨越问题

基于token:

JWT与Session的差异相同点是,他们都是存储用户信息。然而Session是在服务器端的,而JWT是在客户端的

JWT方式将用户状态分散到了客户端中,可以明显减轻请服务器的内存压力,服务端只需要用算法解析客户端的token就可以得到信息

两者优缺点的对比

解析方法:JWT使用算法直接解析得到用户信息;Session需要额外的数据映射。实现匹配

管理方法:JWT只有过期时间的限制,Session 数据保存在服务器,可控性更强

跨平台:JWT就是一段字符串,可以任意传播,Session跨平台需要有统一的解析平台,较为繁琐

时效性:JWT一旦生成 独立存在,很难做到特殊的控制;Session时效性完全由服务端的逻辑说了算

TIPS :各自都有优缺点,都是登陆和授权的解决方案

<END>

推荐阅读:

ULID - 一种比UUID更好的方案

图解 Docker 架构,傻瓜都能看懂!

互联网初中高级大厂面试题(9个G)内容包含Java基础、JavaWeb、MySQL性能优化、JVM、锁、百万并发、消息队列、高性能缓存、反射、Spring全家桶原理、微服务、Zookeeper......等技术栈!⬇戳阅读原文领取!朕已阅

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。