单点登录及权限
分布式系统要实现单点登录,通常将认证系统独立抽取出来,并且将用户身份信息存储在单独的存储介质中,考虑到性能需求,通常存储在redis中。
本项目使用Spring security Oauth2
Oauth2
这是一个用户使用微信第三方登录的流程示意图,采用Oauth2认证
用户访问登录页面,点击微信登录,用户自己是在微信里信息的资源拥有者,这个时候出现一个二维码授权页面,用户用微信扫描确认授权,微信会对用户进行身份验证,验证通过后会给系统办法一个授权码,并且重定向到xx系统页面。客户端获取到授权码,请求认证服务获取令牌,认证服务验证了授权码 颁发令牌。客户端获取到令牌访问资源网站获取用户基本信息,资源服务器返回受保护的资本信息。
Oauth2包括一下角色
客户端 就是流程中的xx系统
资源拥有者 就是流程中的用户
授权服务器 微信认证服务器
资源服务器 微信用户信息服务器
Oauth2在本项目中的使用,
学城前端访问微服务,sso单点登录
微服务之间互相通信
Spring security Oauth2
本项目认证基于Spring security Oauth2进行构建,采用JWT令牌机制,自定义了身份用户信息
Oauth2有以下模式
授权码模式
上面举得例子就是 授权码模式 流程
客户端请求第三方授权。
用户(资源拥有者同意授权)
客户端获取到授权码,请求认证服务器申请令牌
认证服务期响应客户端响应令牌
客户端请求资源服务器 资源服务器校验令牌合法性 完成授权
资源服务器响应资源
请求get拿到授权码
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方法加密后的密文。
请求post拿到令牌
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 jwt是一个开放的行业标准,它定义了一种简洁的、自包含的协议格式,用户在通信双方传递json对象。jwt可以使用hmac算法或者rsa的公钥/私钥来签名,放指篡改
优点
基于json 非常方便解析
可以在令牌中自定义丰富的内容,易扩展
通过非堆成加密算法及数字签名技术,防止篡改,安全性高
资源服务依赖jwt不必使用认证服务即可完成授权
缺点
令牌较长,占储存空间较大
jwt令牌的结构
jwt令牌由三部分组成每部分中间由.分割
herder 头部 包括jwt及其使用的哈希算法
Payload 负载 内容也是一个json对象 可以存放jwt提供的现成字段,也可以自定义字段。此部分不建议存放敏感信息,应为此部分可以解码位原始内容,最后将第二部分使用base64url编码,得到jwt令牌的第二部分
Signature 第三部分是签名。这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名
base64UrlEncode(header) :jwt令牌的第一部分。base64UrlEncode(payload):jwt令牌的第二部分。secret:签名所使用的密钥。
生成RSA算法生成密钥证书
使用下面的命令生成证书文件
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网关在本项目的作用
路由
所有的前端请求都是请求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