用户认证
至此我们了解了使用Spring Security进行认证授权的过程,本节实现用户认证功能。
目前各大网站的认证方式非常丰富:账号密码认证、手机验证码认证、扫码登录等。
本项目也要支持多种认证试。
基于的认证流程在研究Spring Security过程中已经测试通过,到目前为止用户认证流程如下:
认证所需要的用户信息存储在用户中心数据库,现在需要将认证服务连接数据库查询用户信息。
在研究Spring Security的过程中是将用户信息硬编码,如下:
我们要认证服务中连接用户中心数据库查询用户信息。
如何使用Spring Security连接数据库认证吗?
前边学习Spring Security工作原理时有一张执行流程图,如下图:
用户提交账号和密码由DaoAuthenticationProvider调用UserDetailsService的loadUserByUsername()方法获取UserDetails用户信息。
查询DaoAuthenticationProvider的源代码如下:
UserDetailsService是一个接口,如下:
UserDetails是用户信息接口
我们只要实现UserDetailsService 接口查询数据库得到用户信息返回UserDetails 类型的用户信息即可,框架调用loadUserByUsername()方法拿到用户信息之后是如何执行的,见下图:
首先屏蔽原来定义的UserDetailsService。 (在WebSecurityConfig)
下边自定义UserDetailsService
实现从数据库查询客户数据
数据库中的密码加过密的,用户输入的密码是明文,我们需要修改密码格式器PasswordEncoder,原来使用的是NoOpPasswordEncoder,它是通过明文方式比较密码,现在我们修改为BCryptPasswordEncoder,它是将用户输入的密码编码为BCrypt格式与数据库中的密码进行比对。
将在WebSecurityConfig配置的密码格式器修改
我们通过测试代码测试BCryptPasswordEncoder,如下:
在 Spring Security 的 中,每次调用 方法时,它都会生成一个新的随机盐值(salt)并使用该盐值对密码进行哈希处理。因此,每次为相同的密码生成的哈希值(hash)都会不同,因为每个哈希值都包含了不同的随机盐值。
然而, 的 方法在验证密码时考虑了这一点。它不仅仅是比较两个哈希值是否相等,而是提取出哈希值中的盐值,并使用该盐值对提供的原始密码进行哈希处理,然后比较这个新生成的哈希值和存储的哈希值是否匹配。
修改数据库中的密码为Bcrypt格式,并且记录明文密码,稍后申请令牌时需要。
由于修改密码编码方式还需要将客户端的密钥更改为Bcrypt格式.
在AuthenticationManager对客户端秘钥进行修改
现在重启认证服务。
下边使用httpclient进行测试:
输入正确的账号和密码,申请令牌成功。
输入错误的密码,报错:
Java
{
"error": "invalid_grant",
"error_description": "用户名或密码错误"
}
输入错误的账号,报错:
Java
{
"error": "unauthorized",
"error_description": "UserDetailsService returned null, which is an interface contract violation"
}
用户表中存储了用户的账号、手机号、email,昵称、qq等信息,UserDetails接口只返回了username、密码等信息,如下:
我们需要扩展用户身份的信息,在jwt令牌中存储用户的昵称、头像、qq等信息。
如何扩展Spring Security的用户身份信息呢?
在认证阶段DaoAuthenticationProvider会调用UserDetailService查询用户的信息,这里是可以获取到齐全的用户信息的。由于JWT令牌中用户身份信息来源于UserDetails,UserDetails中仅定义了username为用户的身份信息,这里有两个思路:第一是可以扩展UserDetails,使之包括更多的自定义属性,第二也可以扩展username的内容 ,比如存入json数据内容作为username的内容。相比较而言,方案二比较简单还不用破坏UserDetails的结构,我们采用方案二。
修改UserServiceImpl如下:
重启认证服务,重新生成令牌,生成成功。
我们可以使用check_token查询jwt的内容
JSON
###校验jwt令牌
POST {{auth_host}}/oauth/check_token?token=
响应示例如下,
JSON
{
"aud": [
"res1"
],
"user_name": "{"birthday":"2022-09-28T19:28:46","createTime":"2022-09-28T08:32:03","id":"50","name":"学生1","nickname":"大水牛","password":"$2a$10$0pt7WlfTbnPDTcWtp/.2Mu5CTXvohnNQhR628qq4RoKSc0dGAdEgm","sex":"1","status":"1","username":"stu1","userpic":"http://file.51xuecheng.cn/dddf","utype":"101001"}",
"scope": [
"all"
],
"active": true,
"exp": 1664372184,
"authorities": [
"p1"
],
"jti": "73da9f7b-bd8c-45ac-9add-46b711d11fb8",
"client_id": "c1"
}
user_name存储了用户信息的json格式,在资源服务中就可以取出该json格式的内容转为用户对象去使用。
下边编写一个工具类在各个微服务中去使用,获取当前登录用户的对象。
在content-api中定义此类:
下边在内容管理服务中测试此工具类,以查询课程信息接口为例:
重启内容管理服务:
1、启动认证服务、网关、内容管理服务
2、生成新的令牌
3、携带令牌访问内容管理服务的查询课程接口
统一认证入口
目前各大网站的认证方式非常丰富:账号密码认证、手机验证码认证、扫码登录等。基于当前研究的Spring Security认证流程如何支持多样化的认证方式呢?
1、支持账号和密码认证
采用OAuth2协议的密码模式即可实现。
2、支持手机号加验证码认证
用户认证提交的是手机号和验证码,并不是账号和密码。
3、微信扫码认证
基于OAuth2协议与微信交互,学成在线网站向微信服务器申请到一个令牌,然后携带令牌去微信查询用户信息,查询成功则用户在学成在线项目认证通过。
目前我们测试通过OAuth2的密码模式,用户认证会提交账号和密码,由DaoAuthenticationProvider调用UserDetailsService的loadUserByUsername()方法获取UserDetails用户信息。
在前边我们自定义了UserDetailsService接口实现类,通过loadUserByUsername()方法根据账号查询用户信息。
而不同的认证方式提交的数据不一样,比如:手机加验证码方式会提交手机号和验证码,账号密码方式会提交账号、密码、验证码。
我们可以在loadUserByUsername()方法上作文章,将用户原来提交的账号数据改为提交json数据,json数据可以扩展不同认证方式所提交的各种参数。
首先创建一个DTO类表示认证的参数:
此时loadUserByUsername()方法可以修改如下:
原来的DaoAuthenticationProvider 会进行密码校验,现在重新定义DaoAuthenticationProviderCustom类,重写类的additionalAuthenticationChecks方法。
在config下重写additionalAuthenticationChecks
修改WebSecurityConfig类指定daoAuthenticationProviderCustom
添加一个configure
此时可以重启认证服务,测试申请令牌接口,传入的账号信息改为json数据,如下:
经过测试发现loadUserByUsername()方法可以正常接收到认证请求中的json数据。
有了这些认证参数我们可以定义一个认证Service接口去进行各种方式的认证。
定义或拓展用户信息,为了扩展性让它继承XcUser
定义认证Service 接口
对UserServiceImpl下的loadUserByUsername()进行修改
到此我们基于Spring Security认证流程修改为如下:
上面定义了AuthService认证接口,下边实现该接口实现账号密码认证
修改UserServiceImpl类,根据认证方式使用不同的认证bean
重启认证服务,测试申请令牌接口。
1、测试账号和密码都正确的情况是否可以申请令牌成功。
2、测试密码错误的情况。
3、测试账号不存在情况。
验证码服务
在认证时一般都需要输入验证码,验证码有什么用?
验证码可以防止恶性攻击,比如:XSS跨站脚本攻击、CSRF跨站请求伪造攻击,一些比较复杂的图形验证码可以有效的防止恶性攻击。
为了保护系统的安全在一些比较重要的操作都需要验证码。
验证码的类型也有很多:图片、语音、手机短信验证码等。
本项目创建单独的验证码服务为各业务提供验证码的生成、校验等服务。
拷贝课程资料目录xuecheng-plus-checkcode验证码服务工程到自己的工程目录。
定义nacos配置文件
注意修改bootstrap.yml中的命名空间为自己定义的命名空间。
配置redis-dev.yaml,
内容如下:
XML
spring:
redis:
host: 192.168.101.65
port: 6379
password: redis
database: 0
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 0
timeout: 10000
#redisson:
#配置文件目录
#config: classpath:singleServerConfig.yaml
验证码接口测试
验证码服务对外提供的接口有:
1、生成验证码
2、校验验证码。
验证码服务如何生成并校验验证码?
拿图片验证码举例:
1、先生成一个指定位数的验证码,根据需要可能是数字、数字字母组合或文字。
2、根据生成的验证码生成一个图片并返回给页面
3、给生成的验证码分配一个key,将key和验证码一同存入缓存。这个key和图片一同返回给页面。
4、用户输入验证码,连同key一同提交至认证服务。
5、认证服务拿key和输入的验证码请求验证码服务去校验
6、验证码服务根据key从缓存取出正确的验证码和用户输入的验证码进行比对,如果相同则校验通过,否则不通过。
根据接口分析,验证码服务接口如下:
1、生成验证码接口
Plain Text
### 申请验证码
POST {{checkcode_host}}/checkcode/pic
aliasing:为图片
2、校验验证码接口
根据生成验证码返回的key以及日志中输出正确的验证码去测试。
Bash
### 校验验证码
POST {{checkcode_host}}/checkcode/verify?key=checkcode4506b95bddbe46cdb0d56810b747db1b&code=70dl
账号密码认证
到目前为止账号和密码认证所需要的技术、组件都已开发完毕,下边实现账号密码认证,输出如下图:
执行流程如下:
账号密码认证开发
1、在认证服务定义远程调用验证码服务的接口
降级方法
启动类要加上注解
2、完善PasswordAuthServiceImpl
小技巧:目前账号密码方式添加了验证码校验,为了后期获取令牌方便可以重新定义一个不需要验证码校验的认证类AuthService ,AuthService 中去掉验证码的校验,方便生成令牌。
账号密码认证测试 1、使用浏览器访问 http://www.51xuecheng.cn/sign.html
2、首先测试验证码,分别输入正确的验证码和错误的验证码进行测试
3、输入正确的账号密码和错误的账号密码进行测试
登录成功将jwt令牌存储cookie.
4、测试自动登录
勾选自动登录cookie生成时间为30天,不勾选自动登录关闭浏览器窗口后自动删除cookie。
接入规范
接入流程
微信扫码登录基于OAuth2协议的授权码模式,
接口文档:
流程如下:
第三方应用获取access_token令牌后即可请求微信获取用户的信息,成功获取到用户的信息表示用户在第三方应用认证成功。
请求获取授权码
第三方使用网站应用授权登录前请注意已获取相应网页授权作用域(scope=snsapi_login),则可以通过在 PC 端打开以下链接: https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“该链接无法访问”,请检查参数是否填写错误,如redirect_uri的域名与审核时填写的授权域名不一致或 scope 不为snsapi_login。
参数说明
用户允许授权后,将会重定向到redirect_uri的网址上,并且带上 code 和state参数
Plain Text
redirect_uri?code=CODE&state=STATE
若用户禁止授权,则不会发生重定向。
登录一号店网站应用 打开后,一号店会生成 state 参数,跳转到 https://open.weixin.qq.com/connect/qrconnect?appid=wxbdc5610cc59c1631&redirect_uri=https%3A%2F%2Fpassport.yhd.com%2Fwechat%2Fcallback.do&response_type=code&scope=snsapi_login&state=3d6be0a4035d839573b04816624a415e#wechat_redirect 微信用户使用微信扫描二维码并且确认登录后,PC端会跳转到 https://test.yhd.com/wechat/callback.do?code=CODE&state=3d6be0a40sssssxxxxx6624a415e
为了满足网站更定制化的需求,我们还提供了第二种获取 code 的方式,支持网站将微信登录二维码内嵌到自己页面中,用户使用微信扫码授权后通过 JS 将code返回给网站。 JS微信登录主要用途:网站希望用户在网站内就能完成登录,无需跳转到微信域下登录后再返回,提升微信登录的流畅性与成功率。 网站内嵌二维码微信登录 JS 实现办法:
步骤1:在页面中先引入如下 JS 文件(支持https):
Plain Text
http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js
步骤2:在需要使用微信登录的地方实例以下 JS 对象:
Plain Text
var obj = new WxLogin({
self_redirect:true,
id:"login_container",
appid: "",
scope: "",
redirect_uri: "",
state: "",
style: "",
href: ""
});
通过 code 获取access_token
通过 code 获取access_token
Plain Text
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
正确的返回:
Plain Text
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
参数说明
错误返回样例:
Plain Text
{"errcode":40029,"errmsg":"invalid code"}
4.1.4 通过access_token调用接口
获取access_token后,进行接口调用,有以下前提:
Plain Text
access_token有效且未超时;
微信用户已授权给第三方应用帐号相应接口作用域(scope)。
对于接口作用域(scope),能调用的接口有以下:
其中snsapi_base属于基础接口,若应用已拥有其它 scope 权限,则默认拥有snsapi_base的权限。使用snsapi_base可以让移动端网页授权绕过跳转授权登录页请求用户授权的动作,直接跳转第三方网页带上授权临时票据(code),但会使得用户已授权作用域(scope)仅为snsapi_base,从而导致无法获取到需要用户授权才允许获得的数据和基础功能。 接口调用方法可查阅《微信授权关系接口调用指南》
获取用户信息接口文档:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
接口地址
Plain Text
http请求方式: GET
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
响应:
说明如下:
Plain Text
参数 说明
openid 普通用户的标识,对当前开发者帐号唯一
nickname 普通用户昵称
sex 普通用户性别,1为男性,2为女性
province 普通用户个人资料填写的省份
city 普通用户个人资料填写的城市
country 国家,如中国为CN
headimgurl 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
privilege 用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
unionid 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的。
准备开发环境
添加应用
1、注册微信开放平台
2、添加应用
进入网站应用,添加应用
3、添加应用需要指定一个外网域名作为微信回调域名
审核通过后,生成app密钥。
最终获取appID和AppSecret
内网穿透
我们的开发环境在局域网,微信回调指向一个公网域名。
如何让微信回调请求至我们的开发计算机上呢?
可以使用内网穿透技术,什么是内网穿透?
内网穿透简单来说就是将内网外网通过隧道打通,让内网的数据让外网可以获取。比如常用的办公室软件等,一般在办公室或家里,通过拨号上网,这样办公软件只有在本地的局域网之内才能访问,那么问题来了,如果是手机上,或者公司外地的办公人员,如何访问到办公软件呢?这就需要内网穿透工具了。开启隧道之后,网穿透工具会分配一个专属域名/端口,办公软件就已经在公网上了,在外地的办公人员可以在任何地方愉快的访问办公软件了~~
1、在内网穿透服务器上开通隧道,配置外网域名,配置穿透内网的端口即本地电脑上的端口。
这里我们配置认证服务端口,最终实现通过外网域名访问本地认证服务。
2、在本地电脑上安装内网穿透的工具,工具上配置内网穿透服务器隧道token。
市面上做内网穿透的商家很多,需要时可以查阅资料了解下。(如netapp)
接入微信登录
接入分析
根据OAuth2协议授权码流程,结合本项目自身特点,分析接入微信扫码登录的流程如下:
本项目认证服务需要做哪些事?
1、需要定义接口接收微信下发的授权码。
2、收到授权码调用微信接口申请令牌。
3、申请到令牌调用微信获取用户信息
4、获取用户信息成功将其写入本项目用户中心数据库。
5、最后重定向到浏览器自动登录。
定义接口
参考接口规范中“请求获取授权码” 定义接收微信下发的授权码接口,
定义WxLoginController类,如下:
定义微信认证的service
接口环境测试
接口定义好下边进行测试下,主要目的是测试接口调度的环境。
1、启动内网穿透工具
2、在/wxLogin接口中打断点
3、打开前端微信扫码页面
点击微信图标打开二维码
用户扫码,确认授权
此时正常进入 /wxLogin 方法,最后跳转到http://www.51xuecheng.cn/sign.html?username=t1&authType=wx。
申请令牌
接下来请求微信申请令牌。
使用nacos在认证服务的配置文件中配置
1、使用restTemplate请求微信,配置RestTemplate bean
在认证服务的启动类配置restTemplate
定义与微信认证的service接口:
继续在WxAuthServiceImpl类写WxAuthService 接口的实现,在其中定义申请令牌的私有方法并由wxAuth方法去调用:
下边在controller中调用wxAuth接口(将之前的硬编码替换):
1、在wxAuthService中获取用户信息处打断点
3、手机扫码并授权,观察是否成功申请到令牌
获取用户信息
在WxAuthServiceImpl类中定义获取用户信息方法:
完善wxAuth
1、在获取用户信息处打断点
3、手机扫码授权
保存用户信息
向数据库保存用户信息,如果用户不存在将其保存在数据库。
调用保存用户信息
测试保存用户信息
1、在保存用户信息处打断点
本文地址:http://syank.xrbh.cn/quote/6111.html 迅博思语资讯 http://syank.xrbh.cn/ , 查看更多