XC-16 SpringSecurity Oauth2 JWT

news2024/11/26 21:41:28

SpringSecurityOauth2

  • 用户认证需求分析
    • 用户认证与授权
    • 单点登录需求
    • 第三方认证需求
  • 用户认证技术方案
    • 单点登录技术方案
    • Oauth2认证
      • Oauth2认证流程
      • 2.2.2Oauth2在本项目中的应用
    • SpringSecurity Oauth2认证解决方案
  • SpringSecurityOauth2研
    • 目标
    • 搭建认证服务器
      • 导入基础工程
      • 创建数据库
    • Oauth2授权码模式
      • Oauth2授权码模式
      • 授权码授权流程
      • 申请授权码
      • 申请令牌
      • 资源服务授权
        • 资源服务授权流程
        • 资源服务授权配置
        • 资源服务授权测试
          • 解决swagger-ui无法访问
    • Oauth2密码模式授权
    • 校验令牌
    • 刷新令牌
    • JWT研究
      • JWT介绍
        • 令牌结构
      • JWT入门
        • 生成私钥和公钥
        • 生成JWT令牌
        • 验证JWT令牌
  • 认证接口开发
    • 需求分析
    • Redis配置
      • 安装Redis
      • redis连接配置
      • 测试
    • 认证服务
      • 需求分析
      • Api接口
      • 配置参数
      • 申请令牌测试
      • Dao
      • Service
      • Controller
      • 登录url放行
      • 测试认证接口
      • 测试写入Cookie

用户认证需求分析

无论在什么时候,都要将文字信息图像化在自己的大脑中,将之连接贯通这样子的效果才是最好的

用户认证与授权

截至目前,项目已经完成了在线学习功能,用户通过在线学习页面点播视频进行学习,如何去记录学生的学习过程呢?逍遥掌握学生的学习情况就需要知道用户的身份信息,记录那个用户在什么时间学习社么课程;如果用户要够吗i课程也需要知道用户的身份信息,所以,去管理学生的学习过程最基本的要实现省份认证.
什么是身份认证?
用户身份认证及用户取访问系统容资源是系统要求验证用户的身份信息,身份合法放可以继续访问,常见的用户身份信息表现形式有:用户名密码登录,指纹打卡等方式.
什么是用户授权?
用户认证通古今哦后取访问系统的资源,系统会判断用户是否拥有访问资源的全新啊,只允许有全新啊的系统资源,没有权限的资源将无法访问,这个过程叫用户授权.

单点登录需求

本项目包括了多个子项目,如:学习系统,教学管理中心,系统管理中心等,为了提高用户体验性,需要实习那用户之认证一次便可以在多个拥有访问权限的系统中访问,这个功能叫做单点登录.
引用百度百科:单点登录(SingleSign On),简称SSo 是目前比较流行的企业业务整合的解决方案之一.
SSO的定义实在多个应用系统中,用户只需要登录一次就可以访问所有的相互信任的应用系统中
下图是SSo的示意图,用户登录学成网,一次即可访问多个系统.
在这里插入图片描述

第三方认证需求

作为互联网项目,难免需要访问外部系统的资源,同样本系统也要访问第三方系统的资源接口,一个场景如下:
一个微信用户没有学成在线注册,本系统可以通过请求微信系统来验证该用户的身份,通过验证之后,便可以在本系统中学习,他的基本流程如下:
在这里插入图片描述
从上图可以看出,微信不属于本系统,本系统并没有存储微信用户的账号,密码等信息,本系统如果想要获取该用户的基本信息,则需要首先通过微信的认证系统(微信认证)进行认证,微信认证通过之后,本系统便可以获取噶微信用户的基本信息,从而咋i本系统将该微信用户的头像,昵称等信息显示出来,改用户便不用在本系统注册却可以直接学习.
什么是第三方额认证 ?(跨平台认证)
当需要访问第三方的系统的资源时,需要首先通过第三方系统的认证(例如:微信认证),由于第三方系统对用户认证通过,并授权资源的访问权限.
在这里插入图片描述

用户认证技术方案

单点登录技术方案

分布式西药要实现单点登录,通常将认证系统独立抽取出来,并且将用户身份信息存储在大度的储存介质中,比如MySql,Redis,考虑性能要求,通常存储在Redis中,如下图:
在这里插入图片描述
单点登录的特点时:
1.认证系统为独立的系统.
2.各子系统通过Http或者其他协议与认真那个系统通信,完成用户的认证.
3.用户身份信息存储在Redis集群.
Java中有很多用户认证的框架都可以实现单点登录:
1.Apache Shiro
2.CAS
3.Spring Security CAS

Oauth2认证

Oauth2认证流程

第三方认证技术方案最主要的是解决认证协议的通用标准问题,因为要实现跨系统认证,各系统之间要遵循一定的接口协议.
Oauth认证服务,任何服务提供上都可以实现自身的Oauth认证服务,因而Oauth是开放的.业界提供了Oauth的多种实现,如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间,因而Oauth是建议的,互联网很多服务如OpenAPi很多大公司如Goole,Yahoo,Microsoft等都提供了Oauth认证服务,这些足以证明Oauth标准逐渐成为开放资源授权的标准.
Oauth协议目前发展到2.0版本,1.0版本过于复杂,2.0版本已经的大哦广泛的应用.
参考: https://baike.baidu.com/item/oAuth/7153134?fr=aladdin
Oauth协议: https://tools.ietf.org/html/rfc6749
下边分析一个Oaauth2认证的例子,黑马程序员网站的使用微信认证的流程:

在这里插入图片描述

1.客户端请求第三方授权
用户进入黑马程序的登录页面,点击微信的图标以微信账号登录系统,用户是自己在微信里信息资源的拥有者.
在这里插入图片描述
点击微信,出现一个二维码,此时用户扫描二维码,开始给黑马程序员授权.

在这里插入图片描述
2.资源拥有者同意给客户端授权

资源拥有者扫描二维码表示资源拥有者同意给客户端授权,微信会对自由按拥有着的身份进行验证,验证通过之后,微信会寻味用户是否给授权黑马程序员访问自己的微信数据,用户点击确认登陆表示同意授权,微信认证服务器会办法一个授权码,并重定向到黑马程序员的网站.
在这里插入图片描述
3.客户端获取到授权码,请求整整服务器申请令牌
此后过程用户看不到,客户端应用程序请求证人服务,请求携带授权码.
4.认证服务器向客户端相应令牌
认证服务器验证了客户端请求的授权码,如果合法,则给客户端办法令牌,令牌是客户端访问资源的通行证.
此交互过程用户看不到,当客户端拿到令牌之后,用户在黑马程序员看到已经成功登录.
5.客户端请求资源服务器的资源
客户端携带令牌访问资源服务器的资源.
河马程序员网站携带令牌请求访问微信服务器获取用户的基本信息.
6.资源服务器返沪i受保护资源
资源服务器校验令牌的合法性,如果合法,则向用户相应资源信息内容.
注意:资源服务器和认证服务器可以是一个服务器也可以分开的服务,如果是分开的服务资源通常要请求认证服务器来校验令牌的合法性.
在这里插入图片描述
Oaoth2包含以下角色:
1.客户端:
本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:学成在线Android客户端,学成在线Web客户端(浏览器端),微信客户端等.
2.资源拥有者
通常为用户,也可以是应用程序,技改资源的拥有者.
3.授权服务器(也称认证服务器)
用来对资源拥有的身份进行认证,对方问资源进行授权.客户端想要访问资源需要通过认证服务器由资源哟另有这授权后放可以访问.
4.资源服务器
存储资源的服务器,比如,学成网用户管理服务器处处了学成网用户的信息,学成网学生的学习信息,微信的资源服务存储了微信的用户信息等.客户端最总访问资源服务器获取资源信息.
系统想要访问其他第三方系统的资源的时候,就可以走oauth2协议

2.2.2Oauth2在本项目中的应用

Oauth2是一个标准的开放的协议,应用程序可以根据自己的要求去使用Oauth2,本姓穆使用Oauth2实现如下目标:
1.学成在线访问的第三方系统资源
2.外部系统ing访问学成在线的资源
3.学成前端(客户端)访问学成在吸纳微服务的资源.例如:微服务A访问微服务B的资源,B访问A的资源.

SpringSecurity Oauth2认证解决方案

本项目采用Spring Security+ Oauth完成用户认证及用户授权,Spring Security是一个强大的高度可定制的身份证验证和访问控制框架,SPirng Security框架集成了Oauth2协议,下图是项目认证架构图:
在这里插入图片描述
1.用户请求认账服务,完成认证.
2.认证服务下发用户身份令牌,拥有身份你令牌表示用户身份合法
3.用户携带令牌请求资源服务,请求资源服务必须先要经过网关.
4.网管校验用户省份令牌的合法性,不合法表示用户没有登录,如果合法,则放行继续访问.
5.资源服务获取令牌,根据令牌完成授权.
6.资源服务完成授权则相应资源信息.

SpringSecurityOauth2研

目标

本项目认证服务基于SPirngSecurityOauth2进行构建,并且在其基础上做了一些拓展,采用JWT令牌机制,并自定义了用户身份信息内容,本教程的主要目标是学习在项目中集成SPirngSecurityOauth2.的方法和流程,通过SPirngSecurity的研究需要达到以下目标:
1.理解Oauth2的授权码认证流程及密码的流程.
2.理解SPringSecurityOauth2的工作流程.
3.掌握资源服务器集成SpringSecuritykuangjia完成Oauth2认证的流程.

搭建认证服务器

导入基础工程

导入“资料”目录下的xc-service-ucenter-auth工程,该工程是基于SpringSecurityOauth2的一个二次封装的工程,导入此工程研究Oauth2认证流程。

创建数据库

导入资料目录下的xc_user.sql,创建用户数据库
在这里插入图片描述以“oauth_”开头的表都是springSecurity自带的表。
本项目中springSecurity主要使用oauth_client_details表:
在这里插入图片描述
client_id:客户端idresource_ids:资源id(暂时不用)client_secret:客户端密码scope:范围
access_token_validity:访问token的有效期(秒)
refresh_token_validity:刷新token的有效期(秒)authorized_grant_type:授权类型,authorization_code,password,refresh_token,client_credentials

Oauth2授权码模式

Oauth2.有以下授权模式:
授权码模式(AuthorizationCode)
隐式授权模式(Implicit)
密码模式(ResourceOwnerPasswordCredentials)
客户端模式(ClientCredentials)
其中授权码模式和密码是模式应用较多,本小姐介绍授权码模式.

Oauth2授权码模式

上边例举的黑马程序员网站使用微信认证的过程就是授权码模式,流程如下:
1.客户端请求第三方认证授权
2.用户(资源拥有者)同意给客户端授权
3.客户端获取到授权码,请求认证服务器申请令牌
4.认证服务器向客户端响应令牌
5.客户端请求资源服务器的资源

授权码授权流程

亲故其认证服务获取授权码:
Get请求:

localhost:40400/auth/oauth/autorize?
client_id = XcWebApp&response_type=code&app&redirect_uri=http://localhost

参数列表如下:
client_id : 客户端id,和授权配置类中设置的客户端id一致.
response_type: 授权码模式固定为code
scop:客户端范围,和授权配置类中设置的scop一致.
redirect_uri:跳转uri,当授权码申请成功后会跳到此地址,并且在后边带上code参数(授权码).
首先跳转到登录页面:

申请授权码

请求认证服务获取授权码:Get请求:

localhost:40400/auth/oauth/authorize?
client_id=XcWebApp&response_type=code&scop=app&redirect_uri=http://localhost

参数列表如下:
client_id:客户端id,和授权配置类中设置的客户端id一致。
response_type:授权码模式固定为code
scop:客户端范围,和授权配置类中设置的scop一致。
redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)
在这里插入图片描述

接下来进入授权页面
在这里插入图片描述
点击“同意”。
接下来返回授权码:
认证服务携带授权码跳转redirect_ur

申请令牌

拿到授权码后,申请令牌。Post请求:http://localhost:40400/auth/oauth/token参数如下:
grant_type:授权类型,填写authorization_code,表示授权码模式code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。
此链接需要使用httpBasic认证。什么是httpBasic认证?
http协议定义的一种认证方式,将客户端id和客户端密码按照“客户端ID:客户端密码”的格式拼接,并用base64编码,放在header中请求服务端,一个例子:
Authorization:BasicWGNXZWJBcHA6WGNXZWJBcHA=WGNXZWJBcHA6WGNXZWJBcHA=是用户名:密码的base64编码。认证失败服务端返回401Unauthorized
以上测试使用postman完成:httpbasic认证:
在这里插入图片描述
客户端Id和客户端密码会匹配数据库oauth_client_details表中的客户端id及客户端密码。
Post请求参数:
在这里插入图片描述

在这里插入图片描述
点击发送:
申请令牌成功:
在这里插入图片描述

资源服务授权

资源服务拥有要访问的受保护资源,客户端携带令牌访问资源服务,如果令牌合法,则可成功访问资源服务中的资源,如下图:
在这里插入图片描述
上图的业务流程如下:
1.客户端请求认证服务申请令牌
2.认证服务生成令牌
认证服务采用非堆成加密算法,使用私钥生成令牌
3.客户端携带私钥访问资源令牌
客户端在Httpheader中添加:Authorization:bearer令牌
4资源服务请求认证服务校验令牌的有效性
资源服务接收到令牌,使用公钥验证令牌的合法性
5.令牌如果有效,资源服务响应客户端信息

资源服务授权流程

资源服务授权配置

基本上所有微服务都是资源服务,这里在课程管理服务商授权控制,当配置了授权之后,访问课程信息需要提供令牌

资源服务授权测试

解决swagger-ui无法访问

Oauth2密码模式授权

校验令牌

刷新令牌

JWT研究

JWT介绍

在介绍JWT之前,先看一下传统校验令牌的方法,如下图:
在这里插入图片描述
问题:
在传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性,并且根据令牌获取用户的相关信息,性能低下.
解决:
使用JWT的思路,用户认证通过会得到一个JWT的令牌,JWT的令牌里面包括了用户的信息,客户端只要携带JWT令牌访问资源服务,资源服务按照实现约定的算法进行验证,无需每次请求 认证服务完成授权.JWT令牌授权的过程:

JWT:
在这里插入图片描述
什么是JWT?
JSON Web Token(JWT)诗意个开放的行业标准准(RFC7519),他定义了一种简单的,自包含的协议格式,用于在通信双发传递json对象,床底的信息经过数字其那名可以被验证和信任.JWT可以使用HMAC算法或者使用RAS的公钥/私钥来针对签名,防止篡改.
官网: https://jwt.io/
标准: https://tools.ietf.org/html/rfc7519
JWT令牌的优点:
1.jwt基于json,非常方便解析.
2.可以在令牌中自定义丰富的内容,易拓展
3.通过对称加密算法和数字签名技术,JWT防止篡改,安全性高.

缺点:
1、JWT令牌较长,占存储空间比较大。

令牌结构

通过学习JWT令牌结构为自定义jwt令牌打好基础。
JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzzHeader
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMACSHA256或RSA)一个例子如下:
下边是Header部分的内容

{
"alg":"HS256","typ":"JWT"
}

将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。
Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳),sub(面向的用户)等,也可自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
一个例子:

{
"sub":"1234567890","name":"456","admin":true
}

Signature
第三部分是签名,此部分用于防止jwt内容被篡改。
这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。
一个例子:

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

base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:签名所使用的密钥。

JWT入门

JWT令牌生成采用非堆成加密算法
下边命令生成密钥证书,采用RSA算法,每个整数包含公钥和私钥
keytool -genkeypair -alias xckey -keyalg RSA -keypass xuecheng -keystore xc.keystore -storepass xuechengkeystore
Keytool 是一个java提供的证书管理工具
-alias : 明耀的别名
-keyalg: 使用的hash的算法
-keypass :密钥的访问明码
-keystore: 密钥库文件名,xckeystore保存了生成的证书
_store pass: 密钥库的访问密码
在这里插入图片描述

查询证书的信息:
keytool -list -keystore xc.keystore
在这里插入图片描述
输入密钥即可得到内容

生成私钥和公钥

生成JWT令牌

在认证工程创建测试类,测试jwt令牌的生成与验证。

验证JWT令牌

认证接口开发

在这里插入图片描述
执行流程
1.用户挡路,请求认证服务
2.认证服务通过,生成jwt令牌,将jwt令牌及相关信息写入Redis, 并且将身份令牌写入cookie
3.用户访问资源页面,嗲这cookie到网关
4.网关从kookie获取token,并且查询Redis,校验Token,如果Token不存在,则拒绝访问,否则放行
5.用户退出,请求认证服务,清除redis中的token并且删除cookie总的token
使用redis存储用户的身份令牌有以下作用:
1.实现用户退出注销功能,服务端清楚令牌后,即使客户请求携带token也是无效的.
2.由于jwt令牌过长,也不宜存储在cookie中,所以将jwt零阿皮存储在redis,有客户端请求服务端获取并在客户端存储

需求分析

Redis配置

安装Redis

redis连接配置

测试

认证服务

需求分析

在这里插入图片描述

Api接口

在这里插入图片描述

package com.xuecheng.api.auth;

import com.xuecheng.framework.domain.ucenter.request.LoginRequest;
import com.xuecheng.framework.domain.ucenter.response.LoginResult;
import com.xuecheng.framework.model.response.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

/**
 * @author Andrewer
 * @version 1.0
 * @project xcEduService01
 * @description
 * @date 2023/1/27 10:39:15
 */
@Api(value = "用户认证",description = "用户认证接口")
public interface AuthControllerApi {
    @ApiOperation("登录")
    public LoginResult login(LoginResult loginResult);
    @ApiOperation("退出")
    public ResponseResult logout();

}

配置参数

server:
  port: ${PORT:40401}
  servlet:
    context-path: /auth
spring:
  application:
    name: xc-service-ucenter-auth
  redis:
    host: ${REDIS_HOST:127.0.0.1}
    port: ${REDIS_PORT:6379}
    timeout: 5000 #连接超时 毫秒
    jedis:
      pool:
        maxActive: 3
        maxIdle: 3
        minIdle: 1
        maxWait: -1 #连接池最大等行时间 -1没有限制
  datasource:
    druid:
      url: ${MYSQL_URL:jdbc:mysql://localhost:3306/xc_user?characterEncoding=utf-8}
      username: root
      password: root
      driverClassName: com.mysql.jdbc.Driver
      initialSize: 5  #初始建立连接数量
      minIdle: 5  #最小连接数量
      maxActive: 20 #最大连接数量
      maxWait: 10000  #获取连接最大等待时间,毫秒
      testOnBorrow: true #申请连接时检测连接是否有效
      testOnReturn: false #归还连接时检测连接是否有效
      timeBetweenEvictionRunsMillis: 60000 #配置间隔检测连接是否有效的时间(单位是毫秒)
      minEvictableIdleTimeMillis: 300000  #连接在连接池的最小生存时间(毫秒)
auth:
  tokenValiditySeconds: 1200  #token存储到redis的过期时间
  clientId: XcWebApp
  clientSecret: XcWebApp
  cookieDomain: xuecheng.com
  cookieMaxAge: -1
encrypt:
  key-store:
    location: classpath:/xc.keystore
    secret: xuechengkeystore
    alias: xckey
    password: xuecheng
eureka:
  client:
    registerWithEureka: true #服务注册开关
    fetchRegistry: true #服务发现开关
    serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔
      defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/,http://localhost:50102/eureka/}
  instance:
    prefer-ip-address:  true  #将自己的ip地址注册到Eureka服务中
    ip-address: ${IP_ADDRESS:127.0.0.1}
    instance-id: ${spring.application.name}:${server.port} #指定实例id
ribbon:
  MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试,如果eureka中找不到服务则直接走断路器
  MaxAutoRetriesNextServer: 3 #切换实例的重试次数
  OkToRetryOnAllOperations: false  #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false
  ConnectTimeout: 5000  #请求连接的超时时间
  ReadTimeout: 6000 #请求处理的超时时间






申请令牌测试

package com.xuecheng.auth;

import com.alibaba.fastjson.JSON;
import com.xuecheng.framework.client.XcServiceList;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Andrewer
 * @version 1.0
 * @project xcEduService01
 * @description 测试redisDesktop[
 * @date 2023/1/17 13:59:30
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestClient {
//   用来请求eureka的负载均衡
    @Autowired
    LoadBalancerClient loadBalancerClient;
    @Autowired
    RestTemplate restTemplate;
//    远程请求spring security获取令牌

    @Test
public void testClient(){
//       从eureka总获取认证服务的地址(因为spring security在认证服务中)
//        serviceInstance就是一个微服务的实例
//        从eureka中获取一个认证服务的实例地址
        ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
//        此地址就是http://ip:port
        URI uri = serviceInstance.getUri();
//        令牌申请的地址 http://localhost:40400/auth/oauth/token
        String authUrl = uri+"/auth/oauth/token";

//        定义header
        LinkedMultiValueMap<String, String> header = new LinkedMultiValueMap<>();
        String httpBasic = getHttpBasic("XcWebApp", "XcWebApp");
        header.add("Authorization",httpBasic);
//        定义body
        LinkedMultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type","password");
        body.add("username","itcast");
        body.add("password","123");

        HttpEntity<MultiValueMap<String,String>> multiValueMapHttpEntity = new HttpEntity<>(null,null);


//        设置restTemplate远程嗲用用的时候,对400和401不让报错,正确返回数据
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode()!=400&&response.getRawStatusCode()!=401){
                super.handleError(response);

                }
            }
        });

        ResponseEntity<Map> exchange = restTemplate.exchange(authUrl, HttpMethod.POST, multiValueMapHttpEntity, Map.class);

//       申请令牌的信息
        Map bodyMap = exchange.getBody();
        System.out.println(bodyMap);

    }
//        获取httpbasic的串
        private String getHttpBasic(String clientId,String clientSecret){

        String string = clientId+":"+clientSecret;
//        将字符串进行base64编码
            byte[] encode = Base64Utils.encode(string.getBytes());
            return "Basic"+new String(encode);
        }
}

Dao

Service

package com.xuecheng.auth.service;

import com.alibaba.fastjson.JSON;
import com.xuecheng.framework.client.XcServiceList;
import com.xuecheng.framework.domain.ucenter.ext.AuthToken;
import com.xuecheng.framework.domain.ucenter.response.AuthCode;
import com.xuecheng.framework.exception.ExceptionCast;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Andrewer
 * @version 1.0
 * @project xcEduService01
 * @description
 * @date 2023/1/27 15:49:19
 */
public class AuthService {
    //   用来请求eureka的负载均衡
    @Value("${auth.tokenValiditySeconds}")
int tokenValiditySeconds;
    @Autowired
    LoadBalancerClient loadBalancerClient;
   @Autowired
    StringRedisTemplate stringRedisTemplate;
   @Autowired
    RestTemplate restTemplate;
//    远程请求spring security获取令牌


    //    用户认证来申请令牌,将令牌存储到redis
    public AuthToken login(String username, String password, String clientId, String clientSecret) {

//        请求spring Security来申请令牌
        AuthToken authToken = this.applyToken(username, password, clientId, clientSecret);
        if (authToken==null){
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_ERROR);
        }

//        用户身份的令牌
        String access_token = authToken.getAccess_token();
//        存储到redis中的内容
        String jsonString = JSON.toJSONString(authToken);
//        将令牌存储到redis
        boolean result = this.saveToken(access_token, jsonString, tokenValiditySeconds);
        if (!result){
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_ERROR);
        }
        return authToken;
    }
//    存储令牌到redis

    /**
     *
     * @param access_token 用户身份令牌
     * @param content 内容就是AuthToken对象的内容
     * @param ttl   过期的时间
     * @return
     */
    private boolean saveToken(String access_token,String content,long ttl){
        String key = "user_token" + access_token;
      stringRedisTemplate.boundValueOps(key).set(content,ttl, TimeUnit.SECONDS);
//      如果失败了就会返回一个小于零的数字
        Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
        return expire>0;
    }

    //    申请令牌
    private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
//       从eureka总获取认证服务的地址(因为spring security在认证服务中)
//        serviceInstance就是一个微服务的实例
//        从eureka中获取一个认证服务的实例地址
        ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
//        此地址就是http://ip:port
        URI uri = serviceInstance.getUri();
//        令牌申请的地址 http://localhost:40400/auth/oauth/token
        String authUrl = uri + "/auth/oauth/token";

//        定义header
        LinkedMultiValueMap<String, String> header = new LinkedMultiValueMap<>();
        String httpBasic = getHttpBasic(clientId, clientSecret);
        header.add("Authorization", httpBasic);
//        定义body
        LinkedMultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "password");
        body.add("username", username);
        body.add("password", password);

        HttpEntity<MultiValueMap<String, String>> multiValueMapHttpEntity = new HttpEntity<>(null, null);


//        设置restTemplate远程嗲用用的时候,对400和401不让报错,正确返回数据
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
                    super.handleError(response);

                }
            }
        });

        ResponseEntity<Map> exchange = restTemplate.exchange(authUrl, HttpMethod.POST, multiValueMapHttpEntity, Map.class);

//       申请令牌的信息
        Map bodyMap = exchange.getBody();
        if (bodyMap==null ||
            bodyMap.get("access_token")==null ||
                    bodyMap.get("refresh_token")==null ||
                    bodyMap.get("jti")== null
                    ){
                return null;

            }
        AuthToken authToken = new AuthToken();
        authToken.setAccess_token((String) bodyMap.get("jti"));//用户身份令牌
        authToken.setRefresh_token((String) bodyMap.get("refresh_token"));//刷新令牌
        authToken.setAccess_token((String) bodyMap.get("jti"));//jwt令牌
return authToken;
    }




    //    存储令牌到redis
    //        获取httpbasic的串
    private String getHttpBasic(String clientId, String clientSecret) {

        String string = clientId + ":" + clientSecret;
//        将字符串进行base64编码
        byte[] encode = Base64Utils.encode(string.getBytes());
        return "Basic" + new String(encode);
    }
}

Controller

package com.xuecheng.auth.controller;

import com.xuecheng.api.auth.AuthControllerApi;
import com.xuecheng.auth.service.AuthService;
import com.xuecheng.framework.domain.ucenter.ext.AuthToken;
import com.xuecheng.framework.domain.ucenter.request.LoginRequest;
import com.xuecheng.framework.domain.ucenter.response.LoginResult;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;

/**
 * @author Andrewer
 * @version 1.0
 * @project xcEduService01
 * @description
 * @date 2023/1/27 15:45:10
 */
@RestController
@RequestMapping("/")
public class AuthController implements AuthControllerApi {

    @Value("${auth.clientId}")
    String clientId;

    @Value("${auth.clientSecret}")
    String clientSecret;

    @Value("${auth.cookieDomain}")
    String cookieDomain;
    @Value("${auth.cookieMaxAge}")
    int cookieMaxAge;
    @Autowired
    AuthService authService;



    @Override
    @PostMapping("/userlogin")
    public LoginResult login(LoginRequest loginRequest) {
        String username = loginRequest.getUsername();
        String password = loginRequest.getPassword();

//        申请令牌
        AuthToken authToken =  authService.login(username,password,clientId,clientSecret);

//        用户身份令牌
        String access_token = authToken.getAccess_token();

//        将令牌存储到cookie
        this.saveCookie(access_token);
        return new LoginResult(CommonCode.SUCCESS,access_token);
    }

//  将令牌存储到cookie
    private void saveCookie(String token){
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        CookieUtil.addCookie(response,cookieDomain,"/","uid",token,cookieMaxAge,false);
    }
    @Override
    public ResponseResult logout() {
        return null;
    }
}

登录url放行

测试认证接口

测试写入Cookie

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180821.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一起自学SLAM算法:9.2 LSD-SLAM算法

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; 下面将从原理分析、源码解读和安装与运行这3个方面展开讲解LSD-SLAM算法。 9.2.1 LSD-SLAM原理分析 前面已经说过&#xff0c;LSD-SLAM算法是直接法的典型代表。因此在下面的分析中&#xff0c;首先介绍一下直…

学习笔记:Java 并发编程④

若文章内容或图片失效&#xff0c;请留言反馈。 部分素材来自网络&#xff0c;若不小心影响到您的利益&#xff0c;请联系博主删除。 视频链接&#xff1a;https://www.bilibili.com/video/av81461839配套资料&#xff1a;https://pan.baidu.com/s/1lSDty6-hzCWTXFYuqThRPw&am…

CSS语法格式与三种引入方式

文章目录第一章——CSS简介1.1 CSS语法格式1.2 CSS 位置1.3 CSS引入方式1.3.1.行内样式表&#xff08;内联样式表&#xff09;1.3.2 外部样式表1.3.3 内部样式表第一章——CSS简介 1.1 CSS语法格式 CSS 规则由两个主要的部分构成&#xff1a;选择器以及一条或多条声明。 选择…

C语言全局变量和局部变量

局部变量定义在函数内部的变量称为局部变量&#xff08;Local Variable&#xff09;&#xff0c;它的作用域仅限于函数内部&#xff0c; 离开该函数后就是无效的&#xff0c;再使用就会报错。例如&#xff1a;intf1(int a){ int b,c;//a,b,c仅在函数f1()内有效 return abc; } i…

各种CV领域 Attention (原理+代码大全)

人类在处理信息时&#xff0c;天然会过滤掉不太关注的信息&#xff0c;着重于感兴趣信息&#xff0c;于是将这种处理信息的机制称为注意力机制。 注意力机制分类&#xff1a;软注意力机制&#xff08;全局注意&#xff09;、硬注意力机制&#xff08;局部注意&#xff09;、和…

打工人必知必会(三)——经济补偿金和赔偿金的那些事

目录 参考 一、经济补偿金&赔偿金-用人单位承担赔偿责任 1、月平均工资是税前还是税后工资&#xff1f; 3、经济补偿金是否要交个人所得税&#xff1f;如何交&#xff1f; 二、劳动者承担赔偿责任 三、劳动者需要特别注意 参考 《HR全程法律顾问&#xff1a;企业人力资…

Day12 XML配置AOP

1 前言前文我们已经介绍了AOP概念Day11 AOP介绍&#xff0c;并将其总结如下&#xff1a;2 AOP 标签和expression表达式学习<?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:x…

3.4只读存储器ROM

文章目录一、引子二、介绍1.MROM2.PROM3.EPROM4.Flash Memory5.SSD三、运行过程四、回顾一、引子 这一小节&#xff0c;我们学习只读存储器ROM。 上一小节&#xff0c;学习了两种RAM芯片&#xff0c;分别是SRAM和DRAM。详情请戳&#xff1a;3.3Sram和Dram RAM芯片可以支持随…

Pygame创建界面

今天开始对Python的外置包pygame进行学习&#xff0c;pygame是Python的游戏包&#xff0c;使用该包可以设计一些简单的小游戏。 前言 利用Python外置包创建一个简单界面&#xff0c;首先需要下载Python外置包pygame 使用语句&#xff1a;pip install pygame Display模块 创建…

红黑树知识点回顾

Rudolf Bayer 于1978年发明红黑树&#xff0c;在当时被称为对称二叉 B 树(symmetric binary B-trees)。后来&#xff0c;在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。 红黑树具有良好的效率&#xff0c;它可在近似O(logN) 时间复杂度下完成插入、删除、…

实验五、任意N进制异步计数器设计

实验五 任意N进制异步计数器设计 实验目的 掌握任意N进制异步计数器设计的方法。 实验要求 一人一组&#xff0c;独立上机。在电脑上利用Multisim软件完成实验内容。 实验内容 说明任意N进制异步计数器的构成方法 设计过程 集成计数器一般都设有清零端和置数输入端&#xff…

3.7动态规划--图像压缩

3.6多边形游戏&#xff0c;多边形最优三角剖分类似&#xff0c;仅仅是最优子结构的性质不同&#xff0c;这个多边形游戏更加具有一般性。不想看了&#xff0c;跳过。 写在前面 明确数组含义&#xff1a; l: l[i]存放第i段长度, 表中各项均为8位长&#xff0c;限制了相同位数…

ElasticSearch - RestClient操作ES基本操作

目录 什么是RestClient hotel数据结构分析 初始化RestClient 创建索引库 删除索引库 判断索引库是否存在 小结 新增文档 查询文档 更新文档 删除文档 批量导入文档 小结 什么是RestClient ES官方提供了各种不同语言的客户端&#xff0c;用来操作ES这些客户端的本质…

Java基础语法——方法

目录 方法概述 方法定义及格式 方法重载 •方法重载概述 •方法重载特点 方法中基本数据类型和引用数据类型的传递 方法概述 ——假设有一个游戏程序&#xff0c;程序在运行过程中&#xff0c;要不断地发射炮弹(植物大战僵尸)。发射炮弹的动作需要编写100行的代码&…

五、在测试集上评估图像分类算法精度(Datawhale组队学习)

文章目录配置环境准备图像分类数据集和模型文件测试集图像分类预测结果表格A-测试集图像路径及标注表格B-测试集每张图像的图像分类预测结果&#xff0c;以及各类别置信度可视化测试集中被误判的图像测试集总体准确率评估指标常见评估指标混淆矩阵PR曲线绘制某一类别的PR曲线绘…

密码学的100个基本概念

密码学的100个基本概念一、密码学历史二、密码学基础三、分组密码四、序列密码五、哈希函数六、公钥密码七、数字签名八、密码协议九、密钥管理十、量子密码2022年主要完成了密码学专栏的编写&#xff0c;较为系统的介绍了从传统密码到现代密码&#xff0c;以及量子密码的相关概…

C语言函数声明以及函数原型

C语言代码由上到下依次执行&#xff0c;原则上函数定义要出现在函数调用之前&#xff0c;否则就会报错。但在实际开发中&#xff0c;经常会在函数定义之前使用它们&#xff0c;这个时候就需要提前声明。所谓声明&#xff08;Declaration&#xff09;&#xff0c;就是告诉编译器…

《网络编程实战》学习笔记 Day9

系列文章目录 这是本周期内系列打卡文章的所有文章的目录 《Go 并发数据结构和算法实践》学习笔记 Day 1《Go 并发数据结构和算法实践》学习笔记 Day 2《说透芯片》学习笔记 Day 3《深入浅出计算机组成原理》学习笔记 Day 4《编程高手必学的内存知识》学习笔记 Day 5NUMA内存知…

【论文翻译】Non-local Neural Networks

摘要 卷积运算和循环运算都是每次处理一个局部邻域的构建块。在本文中&#xff0c;我们将非局部操作作为一组用于捕获长期依赖关系的构建块。受计算机视觉中经典的非局部均值方法[4]的启发&#xff0c;我们的非局部运算将一个位置的响应计算为所有位置特征的加权和。这个构建块…

「自控原理」5.2 频域稳定判据、频域分析

本节介绍奈奎斯特稳定判据、对数稳定判据&#xff0c;并引入稳定裕度 本节介绍频率特性法分析系统性能 本节介绍通过开环频率特性得到闭环频率特性的方法 文章目录频域稳定判据奈奎斯特稳定判据ZP−2NZP-2NZP−2N奈奎斯特稳定判据的推导对数稳定判据容易判断出错的情况临界稳定…