用户的认证和授权

今天简单来介绍一下项目中的用户认证和授权是怎么做的,也是我之前作过的项目中所经历过的一个模块;今天来整理一下,分享一下经验共同来探讨改进这一部分;

先来介绍一下用户的认证和授权所用到的技术点都有那些;这些是我做这个认证和授权时用到的技术点,可能有所欠缺
springSecurity+Oauth2、JWT、BCryptPasswordEncoder

1、概念说明:

什么是用户认证
用户身份认证就是用户去访问系统资源时系统要求验证用户的身份信息,身份合法就可以继续访问;常见的用户身份认证的形式有:账号密码登陆,指纹打卡等方式;
什么是用户授权
用户认证通过后去访问系统的资源时,系统会判断该用户是否拥有访问资源的权限,只允许访问有权限的资源,没有权限的资源将无法访问,这个就是用户授权;
什么是单点登陆
在分布式项目中会包含多个子项目,每个子项目都会接入认证和授权,那么这个时候用户想要访问的时候不能每个子模块都需要登陆一次,为了提高用户体验性需要实现用户只认证一次便可以在多个拥有访问权限的子项目中访问,这个功能就做单点登陆;
单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。
SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统
单点登陆的特点
认证系统为独立的系统;
各子系统通过http或其他协议与认证系统通过,完成用户认证;
用户身份信息存储在redis集群
什么是第三方认证
假如一个用户没有在该平台注册账号,但是拥有微信的账号和个人信息,用户登陆的时候就可以点击微信登陆,本系统去请求微信系统来验证用户的身份信息,在微信拿到该用户的身份信息后就可以继续访问;
当需要访问系统的资源时需要首先通过第三方系统的认证(例如:微信认证),由第三方系统对用户认证通过,并授权资源的访问权限
什么是Oauth2协议
第三方认证技术方案最主要是解决认证协议的通用标问题,因为要实现 跨系统认证,各系统之间要遵循一定的接口协议;OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准,业界提供了oauth的多种实现语言开发包,节约开发时间,因为oauth是简易的很多大公司都提供了这个认证服务,可以说明oauth逐渐成为开发授权的标准
Oauth2的授权模式
授权码模式(Authorization Code) 隐式授权模式(Implicit) 密码模式(Resource Owner Password
Credentials) 客户端模式(Client Credentials)
授权码模式流程
这个模式就是我们常见的网站中的QQ和微信登陆
1、客户端请求第三方授权 2、用户(资源拥有者)同意给客户端授权 3、客户端获取到授权码,请求认证服务器申请令牌 4、认证服务器向客户端响应令牌 5、客户端请求资源服务器的资源,资源服务校验令牌合法性,完成授权6、资源服务器返回受保护资源
密码授权模式
密码模式(Resource Owner Password Credentials)与授权码模式的区别是申请令牌不再使用授权码,而是直接通过用户名和密码即可申请令牌,请求时在请求头中带上申请的令牌就可以访问认证服务
什么是JWT
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性,并根据令牌获取用户的相关信息,性能低下;使用jwt就可以解决这个问题,jwt令牌中包含了用户的信息,资源服务解析得到用户信息就可以自行完成令牌校验,无需每次都请求认证服务完成授权
JWT令牌的优缺点
1、jwt基于json,非常方便解析
2、可以在令牌中自定义丰富的内容,易拓展
3、通过非对称加密算法及数字签名技术,防止篡改,安全性高;
4、资源服务使用jwt可以不依赖认证服务完成授权
缺点:jwt令牌较长,占存储空间比较大
Jwt的构成
JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:签名所使用的**。
Header:
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
下面就是header部分的内容
{
“alg”: “HS256”,
“typ”: “JWT”
}
将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。
Payload:
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,这个部分我们通常保存自定义的信息
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分
例如:
{
“sub”: “1234567890”,
“name”: “456”,
“admin”: true
}
Signature:
第三部分是签名,此部分用于防止jwt内容被篡改。
这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。
例如:
HMACSHA256(
base64UrlEncode(header) + “.” +
base64UrlEncode(payload),
secret)
BCryptPasswordEncoder加密
早期使用md5对密码进行编码,每次算出的md5值都一样,这样非常不安全,Spring Security推荐使用BCryptPasswordEncoder对密码加随机盐,每次的Hash值都不一样,安全性高。
这个盐说的是在加密的时候添加一些其他的佐料改变这个数据的本质,有点添油加醋的意思
解释:基本的加密算法比如md5加密是对密码按照一定的算法将密码变换成一个看不懂的数据但是还是标识这个密码但是缺陷是如果两个人的密码一样那么他加密出来的数据是一样的;md5也可以实现加盐加密但是这个盐是固定的比如说在对密码进行加密的时候将这个用户的账号加进去,这样的缺陷是一旦这个盐泄露了密码同样也是可以被破译的(比如说系统管理人员知道这种规则就可以**用户的密码);
但是BCryptPasswordEncoder这种加密算法就可以避免这种情况,虽然密码一样但是加密出来的数据却是不一样的,因为他加盐加密的时候使用的是随机盐(根据一种算法随机生成),这样的话盐就不会泄露,密码也不会被**;

2、用户认证:

用户认证流程
因为本项目采用了分布式所以必须解决单点登陆的问题,将认证服务单独抽离出来作为一个子模块;该认证服务基于spring Security+Oauth2进行构建;
用户输入账号和密码点击登陆,请求到我们的这个认证服务;认证服务根据用户的账号和密码生成jwt令牌(使用rest Template请求Spring Security申请令牌的时候,会根据用户名判断该用户是否存在,如果存在spring Security会比较密码是否正确,比较成功的话将该用户的信息以及所拥有的权限,设置到jwt中生成令牌),将令牌信息保存到redis中key就是用户身份令牌,再将用户身份信息令牌保存到cookie中之后将用户身份信息令牌返回给前台;用户请求资源时会请求到网关,网关中的过滤器会判断该请求是否合法,当cookie中的身份令牌不为空和header中的jwt令牌不为空并且redis中的令牌也没有过期;这三个条件只要有一个没通过就会拒绝请求,不会将请求向后面的微服务转发,如果满足了条件网关将请求转发到对应的资源服务上,资源服务就会解析该请求头中的jwt令牌是否合法,不合法同样也是不允许访问,合法了继续访问用户请求的资源(前提是资源服务必须纳入spring Security的管理);
微服务之间的认证
当用户访问后台的这些资源服务时(微服务)因为服务已经纳入了spring Security的管理范围,所以需要携带jwt令牌来访问通过认证,那么当使用feign进行服务与服务之间的访问获取数据时也需要携带jwt令牌来通过认证;
微服务之间使用feign进行远程调用,采用feign拦截器实现远程调用携带JWT,这个feign拦截器定义在common工程中,有了feign拦截器每次使用feign调用其他微服务的时候feign都会执行一次这个拦截器,可以将请求头中信息拿出来放到feign请求中,就实现了使用feign调用微服务时通过认证的问题,因为多个微服务都需要使用到嘛,当微服务需要使用时只需要在启动类中定义一个bean即可;
用户的认证和授权
用户的认证和授权

3、用户授权:

用户授权流程:
当用户携带jwt请求的资源服务认证通过后,下一步就会判断用户是否有权限访问资源,拥有权限的方法正常执行,没有权限的方法将拒绝访问;
方法授权
方法授权是在方法上添加注解完成授权功能,一般在控制层中的方法上使用注解指定权限标识,因为后面service或者dao中的方法一般都是公用的,只有controller中的方法才是具体要访问后面service中的什么方法如果控制层都进不去,当然service中的方法肯定也进不去;
在方法上添加 @PreAuthorize(“hasAuthority(‘course_find_list’)”)注解就完成了对这个方法的访问权限标识@PreAuthorize(“hasAuthority(’’)”)是固定的权限标识,course_find_list具体是指拥有这个权限才能访问;
使用方法授权的提前的是需要在spring Security的配置类中**这个功能,如下:
用户的认证和授权
细粒度授权
细粒度授权也叫数据范围授权,即不同的用户所拥有的操作权限相同,但是能够操作的数据范围是不一样的。
例子:用户A和用户B都是教学机构,他们都拥有“我的课程”权限,但是两个用户所查询到的数据是不一样的。
细粒度授权涉及到不同的业务逻辑,通常在service层实现,根据不同的用户进行校验,根据不同的参数查询不同的数据或操作不同的数据。
说白了细粒度授权就跟根据用户的id查询出用户所用户的数据,比如:每个用户都有查询博客的权限,但是查询时需要根据用户的id查询出用户所拥有的博客这个就是细粒度授权
查询条件所需要的用户id可以从jwt中解析获取,因为在认证时生成jwt令牌的时候就将用户id放进去了

4、用户退出登陆:

用户在前台点击退出登陆按钮,会请求到认证服务,认证服务中会从cookie中取出身份信息令牌作为redis的key去删除redis中的该用户的jwt令牌,再将cookie中的身份信息令牌清除,就实现了用户退出登陆操作,此时即使客户端再携带之前的tocken令牌来请求资源服务也是无效的请求;