项目最初的安全方案:使用cookie实现用户登录权限管理
最初,BLER项目主要使用了Cookie进行鉴权。首先,在用户登录时使用用户的信息创建Cookie:
1 | public int loginUser(String userAccount, String password,HttpServletResponse rsp) { |
对于每个用户,登陆时产生两个cookie:token
和id
,存储到用户的浏览器中。
前端请求中包含两个cookie的信息是因为浏览器在发送HTTP请求时,会自动将当前域下所有相关的cookie信息添加到请求头的Cookie
字段中。这意味着只要用户在浏览器上存储了多个与当前网站域名匹配的cookie,浏览器在向该域名发送请求时,就会将这些cookie一起发送给服务器。
所以对于这种鉴权方式,我们不需要额外在前端进行手动设置,而是直接进行后端的验证处理。可以直接在req中获取cookie的信息,作为用户权限的凭证。
1 | const token = req.cookies.token; |
Cookie方案的缺陷
然而,这种方案存在很多缺陷:
安全性能低
这种方案将token与用户id这些敏感信息直接存储在了用户本地浏览器的cookie中,增加了数据泄露的风险。
缺乏令牌刷新机制
为了提高安全性,通常我们不希望令token长期有效,而上面我使用的方案没有定义token定期更新的机制,因此存在安全问题。
另一方面,假如我试图定义这样的机制,则它会比较复杂,并且可能需要用户频繁重新登录,影响用户的体验。
不够标准化,扩展性差
在有Spring Security等完善成熟的机制的情况下,使用cookie的鉴权方式过于简陋,并且对任何新的需求都需要开发者重新考虑。
所以,在重构项目时,我尝试使用Spring Secutiry+JWT这种相对成熟和规范的方式重新实现项目的权限管理。
整合SpringSecurity和JWT实现认证和授权
什么是JWT
JWT
(JSON WEB TOKEN)是基于 RFC 7519 标准定义的一种可以安全传输的的JSON对象,由于使用了数字签名,所以是可信任和安全的。
JWT token的格式如下:
1 | header.payload.signature |
header
中用于存放签名的生成算法;payload
中用于存放用户名、token的生成时间和过期时间;signature
为以header和payload生成的签名,一旦header和payload被篡改,验证将失败。
使用JWT实现认证和授权的原理
- 用户调用登录接口,登录成功后获取到JWT的
token
; - 之后用户每次调用接口都在http的
header
中添加一个叫Authorization
的头,值为JWT的token
; - 后台程序通过对
Authorization
头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。
具体实现
- 在**
pom.xml
**中添加相关依赖;
1 | <dependencies> |
- 修改配置文件**
application.yml
**,添加JWT相关配置;
1 | jwt: |
- 添加JWT token的工具类,用于生成和解析JWT token的工具类;
1 | public class JwtTokenUtil { |
- 添加Spring Security的配置类
1 |
|
其中的RestfulAccessDeniedHandler
,访问接口没有权限时,自定义的返回配置:
1 |
|
其中的RestAuthenticationEntryPoint
未登录或者token失效访问接口时,自定义的返回结果:
1 |
|
其中的IgnoreUrlsConfig
白名单配置:
修改application.yml
文件,添加如下路径配置
1 | secure: |
1 |
|
- 添加自定义权限配置,配置好获取用户信息的服务。
1 |
|
- 让Swagger发送认证请求头,对其配置进行调整
1 |
|
- 具体配置接口权限
1 | /** |
其中,**@PreAuthorize("hasAuthority('brand:list')")
**是Spring Security框架中的注解,用于在运行时对方法调用执行安全检查。
具体来说,@PreAuthorize
会在方法执行之前进行权限验证。这里表达式hasAuthority('admin:listjob')
表示当前认证的用户必须拥有名为’admin:listjob’的权限才能访问该方法。
这种权限字符串本身是自定义的,可以在应用程序代码逻辑中直接使用,可以根据业务需要进行定义,比如可以定义另一个权限为’admin:listcompany’,这有助于实现细粒度的基于角色或权限的安全控制。
遇到的问题,白名单的设置
在初步完成Spring Security的配置后,发现我的swagger API信息连接:
http://localhost:8088/swagger-ui/
无法被正确访问了。检查浏览器console发现,错误为403,本页面被Spring Security拦截了。因此才想到去配置白名单,参见上文内容。
此外,使用JWT时,生成的token并不存储在服务端,也不在客户端。JWT的设计理念是无状态的,这意味着服务器在生成JWT后,会将其作为响应的一部分发送给客户端。相比于直接将Cookie存储在客户端的权限管理方式,这无疑提高了安全性。