分布式系统要实现单点登录,通常将认证系统独立抽取出来,并且将用户身份信息存储在单独的存储介质中,考虑到性能需求,通常存储在redis中。
本项目使用Spring security Oauth2
这是一个用户使用微信第三方登录的流程示意图,采用Oauth2认证
用户访问登录页面,点击微信登录,用户自己是在微信里信息的资源拥有者,这个时候出现一个二维码授权页面,用户用微信扫描确认授权,微信会对用户进行身份验证,验证通过后会给系统办法一个授权码,并且重定向到xx系统页面。客户端获取到授权码,请求认证服务获取令牌,认证服务验证了授权码 颁发令牌。客户端获取到令牌访问资源网站获取用户基本信息,资源服务器返回受保护的资本信息。
Oauth2包括一下角色
客户端 就是流程中的xx系统
资源拥有者 就是流程中的用户
授权服务器 微信认证服务器
资源服务器 微信用户信息服务器
Oauth2在本项目中的使用,
学城前端访问微服务,sso单点登录
微服务之间互相通信
本项目认证基于Spring security Oauth2进行构建,采用JWT令牌机制,自定义了身份用户信息
Oauth2有以下模式
上面举得例子就是 授权码模式 流程
客户端请求第三方授权。
用户(资源拥有者同意授权)
客户端获取到授权码,请求认证服务器申请令牌
认证服务期响应客户端响应令牌
客户端请求资源服务器 资源服务器校验令牌合法性 完成授权
资源服务器响应资源
localhost:40400/auth/oauth/authorize?
client_id=XcWebApp&response_type=code&scop=app&redirect_uri=http://localhost
参数列表
client_id 客户端id,response_type 授权码模式固定位code,scop客户端范围,和授权配置类中设置的一致,redirect_uri跳转url 当申请成功会跳转到此url并且后面跟上授权码
首先会跳转到登录页面,输入客户端的id和密码 链接认证服务器接收到请求会调用spring
security的UserDetailsService接口的loadUserByUsername方法校验客户端的用户名和密码,此客户端用户名和密码在一个继承spring security 的AuthorizationServerConfigurerAdapter方法中配置。密码需要用spring security的Bcrypt方法加密后的密文。
http://localhost:40400/auth/oauth/token
参数列表
grant_type授权类型,填写authorization_code,表示授权码模式 code 授权码,就刚刚获取的授权码,授权码只能使用一次,redirect_uri 跳转url 和刚才的跳转url保持一致。
这个请求需要使用http Basic认证
申请令牌成功
access_token 访问令牌,携带此令牌访问资源,refresh_token 刷新令牌,可以令牌过期时间,expires_in 过期时间,scope 范围
认证服务生成令牌的时候使用非堆成加密算法 使用私钥生成令牌
资源服务使用公钥校验令牌的合法性 合法返回资源 公钥需放置在微服务的classpath目录下
密码模式与授权码模式的区别是申请令牌不需要授权码,而是直接通过用户名和密码即可申请令牌
post请求http://localhost:40400/auth/oauth/token
参数说明 grant_type密码模式授权填写password,username:账号,password:密码,并且此链接需要使用 http Basic认证。
本项目使用密码授权模式
传统授权模式用户每次请求资源,资源服务都需要携带令牌请求认证服务,效率底下。
使用jwt令牌,jwt令牌中包含了用户的相关信息,用户只需要携带jwt令牌,资源服务根据事先约定的算法自行完成令牌的校验。无需每次都请求认证服务
什么是jwt jwt是一个开放的行业标准,它定义了一种简洁的、自包含的协议格式,用户在通信双方传递json对象。jwt可以使用hmac算法或者rsa的公钥/私钥来签名,放指篡改
基于json 非常方便解析
可以在令牌中自定义丰富的内容,易扩展
通过非堆成加密算法及数字签名技术,防止篡改,安全性高
资源服务依赖jwt不必使用认证服务即可完成授权
令牌较长,占储存空间较大
jwt令牌由三部分组成每部分中间由.分割
herder 头部 包括jwt及其使用的哈希算法
Payload 负载 内容也是一个json对象 可以存放jwt提供的现成字段,也可以自定义字段。此部分不建议存放敏感信息,应为此部分可以解码位原始内容,最后将第二部分使用base64url编码,得到jwt令牌的第二部分
Signature 第三部分是签名。这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名
base64UrlEncode(header) :jwt令牌的第一部分。base64UrlEncode(payload):jwt令牌的第二部分。secret:签名所使用的密钥。
使用下面的命令生成证书文件
keytool -genkeypair -alias xckey -keyalg RSA -keypass xuecheng -keystore xc.keystore -storepass xuechengkeystore
Keytool 是一个java提供的证书管理工具
-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名,xc.keystore保存了生成的证书
-storepass:密钥库的访问密码
使用openssl来导出公钥信息
进入xc.keystore文件所在目录执行如下命令
keytool ‐list ‐rfc ‐‐keystore xc.keystore | openssl x509 ‐inform pem ‐pubkey
-----BEGIN PUBLIC KEY-----就是公钥 拷贝到文件中 合并为一行 就是公钥
本项目使用sprin security 提供的JwtHelper来创建jwt令牌,校验jwt令牌
用户登录,请求认证服务
认证服务通过,生成jwt令牌,将jwt令牌写入redis,将身份令牌写入cookie,用户访问资源页面,带着cookie访问到网关,网关从ccokie获取tooken,并且查询redis校验token,通过放行,不通过拒绝访问,用户退出,请求认证服务,清除redis总的token,并且删除cookie中的token
客户端请求认证服务进行认证
认证服务认证通过Spring Security申请令牌,将token和jwt存储在redis中,将token存储在cookie中
前端登录成功携带tooen请求认证服务获取jwt令牌并存储在sessionStorage,并从jwt令牌中解析数据显示在页面
前端请求资源携带两个令牌,一个是cookie中的token身份令牌,一个是sessionStorage中的jwt令牌,前端会在请求资源前在http header上添加jwt请求资源
网关校验token合法性 用户请求必须携带token和jwt,网关校验redis中的token师傅合法,过期了不通过重新登录
资源服务校验jwt令牌,完成授权
网关的作用相当于一个过滤器,拦截器。可以拦截多个系统的请求。
服务网关实在微服务前边设置的一道屏障,请求先到服务网关,服务网关会对请求进行过滤,校验,路由等。有了服务网关可以提高微服务的安全性
zuul网关在本项目的作用
路由
所有的前端请求都是请求zuul的网关url,然后根据规则进行路由转发
过滤器 zuul的核心是过滤器 继承自ZuulFilter抽象类,需要覆盖四个方法
从cookie查询身份令牌是否存在,不存在拒绝访问
http header中查询jwt令牌是否存在,不存在拒绝访问
从redis中查询身份令牌是否过去,过期拒绝访问。
微服务之间使用feign进行远程调用,采用feign拦截器实现远程调用携带jwt
实现
在common工程定义拦截器 该拦截器实现RequestInterceptor接口,RequestInterceptor接口时feign包下的一个接口。
在拦截器中取出request,取出当前请求的herder 取出jwt。将header向下传递。
在有需求的调用方启动类中定义这个bean。
方法授权是资源服务根据jwt令牌完成对方法的授权。
生成jwt令牌时在令牌中写入用户所拥有的权限
给每个权限写一个名字,例如某用户拥有课程查询course_find_list,,,,
在资源服务的jwt配置类ResourceServerConfig上加EnableGlobalMethodSecurity注解
表明激活方法上的PreAuthorize注解
在资源服务的方法上加上PreAuthorize注解 并指定此方法需要的权限,在controller层加
当请求的jwt中有这个权限是可以访问,没有这个权限是拒绝访问。
本项目关于权限有五张表 用户表,角色表 用户角色表 权限表 角色权限表
查询某个用户所拥有的权限
确定用户id 查询用户所拥有的角色 查询角色所拥有的权限 使用子查询
数据范围授权
不通的用户所拥有的方法权限相同,但是能操作的数据范围是不一样的。例如用户一和用户二都是教学机构,他们都拥有我的课程权限,但是两个用户所查询到的数据是不一样的。
细粒度授权涉及到不同的业务逻辑,通常在service层实现,根据不同的用户进行校验,根据不同的参数查询不同的数据或者操作不同的数据。
细粒度授权一般没有现成的框架 举例 我的课程查询
获取当前登录用户的id
获取当前用户所属教育机构的id
查询该教育机构旗下的课程信息
查询我的课程,获取当前request,解析jwt令牌,获取当前用户信息和单位id