密码加密与微服务鉴权JWT

## 学习目标

1、用户注册时候,对数据库中用户的密码进行加密存储(使用 SpringSecurity)。
2、使用 JWT 鉴权认证。

一、BCrypt 密码加密

任何应用考虑到安全,绝不能明文的方式保存密码。密码应该通过哈希算法进行加密。
有很多标准的算法比如SHA或者MD5,结合salt(盐)是一个不错的选择。 Spring Security
提供了BCryptPasswordEncoder类,实现Spring的PasswordEncoder接口使用BCrypt强哈希方法来加密数据库中用户的密码。BCrypt强哈希方法 每次加密的结果都不一样。

二、常见的认证机制

2.1、HTTP Basic Auth
HTTP Basic Auth简单点说就是每次请求API时都提供用户的username和
password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供
用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被
使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic
Auth
2.2 Cookie Auth
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端
的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的
session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删
除。但可以通过修改cookie 的expire time使cookie在一定时间内有效;
2.3 OAuth
OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在
某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和
密码提供给第三方应用。
OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提
供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时
段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这
样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信
息,而非所有内容。
下面是OAuth2.0的流程

这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应
用,但是不太适合拥有自有认证权限管理的企业应用。

2.4 Token Auth

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程如下:

客户端使用用户名跟密码请求登录。
服务端收到请求,去验证用户名与密码。
验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端。
客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里。
客户端每次向服务端请求资源的时候需要带着服务端签发的 Token。
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向
客户端返回请求的数据。
下面是Token Auth 的流程:

重点:Token机制相对于Cookie机制的优缺点?

  • 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提 是传输的用户认证信息通过HTTP头传输.
  • 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为 Token
    自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储 状态信息.
  • 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,
    HTML,图片等),而你的服务端只要提供API即可.
  • 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在
    你的API被调用的时候,你可以进行Token生成调用即可. 更适用于移动应用: 当你的客户端是一个原生平台(iOS,
    Android,Windows 8等) 时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认
    证机制就会简单得多。
  • CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防 范。
  • 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256 计算 的Token验证和解析要费时得多.
  • 不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要 为登录页面做特殊处理.

三、什么是 JSON Web Token(JWT)

JWT 格式组成:头部+载荷+签名 ( header + payload + signature )

头部(Header)
头部用于描述关于该 JWT 的最基本的信息,例如其类型以及签名所用的算法等。可以被表示成一个 JSON 对象。例如以下在头部指明了签名算法是HS256算法。我们进行BASE641编码以下内容:{“typ”:“JWT”,“alg”:“HS256”},得到编码后的字符串如下:
JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=

载荷(playload)
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:

(1) 标准中注册的声明(建议但不强制使用)

iss: jwt签发者。
sub: jwt所面向的用户。
aud: 接收jwt的一方 。
exp: jwt的过期时间,这个过期时间必须要大于签发时间 。
nbf: 定义在什么时间之前,该jwt都是不可用的.。
iat: jwt的签发时间 。
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
(2) 公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.
但不建议添加敏感信息,因为该部分在客户端可解密。

(3) 私有声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64
是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload: {“sub”:“1234567890”,“name”:“John Doe”,“admin”:true},然后将其进行base64编码,得到 Jwt 的第二部分如下:JTdCJTIyc3ViJTIyJTNBJTIyMTIzNDU2Nzg5MCUyMiUyQyUyMm5hbWUlMjIlM0ElMjJKb2huJUEwRG9lJTIyJTJDJTIyYWRtaW4lMjIlM0F0cnVlJTdE

签名(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret

这个部分需要base64加密后的header和base64加密后的payload使用,连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分如下:(就是使用头部指明的签名算法对已经加密了以后的字符串在进行加密得到第三部分)

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用连接成一个完整的字符串,构成了最终的jwt如下:

JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=.JTdCJTIyc3ViJTIyJTNBJTIyMTIzNDU2Nzg5MCUyMiUyQyUyMm5hbWUlMjIlM0ElMjJKb2huJUEwRG9lJTIyJTJDJTIyYWRtaW4lMjIlM0F0cnVlJTdE.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

知识点1:
Signature 部分是对前两部分的签名,防止数据篡改。首先需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后使用 Header 里面指定的签名算法,按照下面的公式产生签名: HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret) 。
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用(.)分隔,就可以返回给用户。
知识点2:
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

四、案例代码演示

需求:删除用户(User),必须拥有管理员(Admin)权限,否则不能删除。

前后端约定:前端请求后端时需要添加头信息 Authorization ,内容为Bearer+空格
+token

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.6.0</version>
</dependency>

用户生成 、解析 token 的工具类

@ConfigurationProperties("jwt.config")
public class JwtUtil {private String key;private long ttl;//一个小时public String getKey() {return key;}public void setKey(String key) {this.key = key;}public long getTtl() {return ttl;}public void setTtl(long ttl) {this.ttl = ttl;}// 生成JWTpublic String createJWT(String id, String subject, String roles) {long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);JwtBuilder builder = Jwts.builder().setId(id).setSubject(subject).setIssuedAt(now).signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);if (ttl > 0) {builder.setExpiration(new Date(nowMillis + ttl));}return builder.compact();}// 解析JWTpublic Claims parseJWT(String jwtStr) {return Jwts.parser().setSigningKey(key).parseClaimsJws(jwtStr).getBody();}}

启动类注入 JwtUtil 工具类

@SpringBootApplication
public class BcryptJwtApplication {public static void main(String[] args) {SpringApplication.run(BcryptJwtApplication.class, args);}@Beanpublic JwtUtil jwtUtil() {return new JwtUtil();}}

创建拦截器类
如果每个方法都去写一段代码验证用户登陆 token 的正确性,冗余度太高不利于维护。我们可以将这段代码放入拦截器去实现同意拦截,再判断用户 token。

Spring为我提供了
org.springframework.web.servlet.handler.HandlerInterceptorAdapter 这个适配器,
继承此类,可以非常方便的实现自己的拦截器。

他有三个方法:
分别实现预处理、后处理(调用了Service并返回ModelAndView,但未进行页面渲
染)、返回处理(已经渲染了页面)。
在preHandle中,可以进行编码、安全控制等处理;
在postHandle中,有机会修改ModelAndView;
在afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录。

@Component
public class JwtInterceptor extends HandlerInterceptorAdapter {@Autowiredprivate JwtUtil jwtUtil;public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {System.out.println("经过拦截器");final String authHeader = request.getHeader("Authorization");//获取头信息if (authHeader != null && authHeader.startsWith("Bearer ")) {   // 注意是 Bearer + 空格final String token = authHeader.substring(7);Claims claims = jwtUtil.parseJWT(token);if (claims != null) {if ("admin".equals(claims.get("roles"))) {//如果是管理员request.setAttribute("admin_claims", claims);}if ("user".equals(claims.get("roles"))) {//如果是普通用户request.setAttribute("user_claims", claims);}}}return true;}
}
配置拦截器类
@Configuration
public class ApplicationConfig extends WebMvcConfigurationSupport {@Autowiredprivate JwtInterceptor jwtInterceptor;protected void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor).addPathPatterns("/**").excludePathPatterns("/**/login");}
}

控制层 controller

@RestController
@CrossOrigin
@RequestMapping("/admin")
public class AdminController {@Autowiredprivate AdminService adminService;@Autowiredprivate JwtUtil jwtUtil;// Admin 用户登陆@RequestMapping(value = "/login", method = RequestMethod.POST)public Result login(@RequestBody Admin admin) {Admin loginUser = adminService.findByLoginNameAndPassword(admin.getLoginname(), admin.getPassword());if (null == loginUser) {return new Result(false, StatusCode.LOGINERROR, "登陆失败,请检查用户名或者密码是否正确");}// 生成令牌,并且返回给前台String token = jwtUtil.createJWT(loginUser.getId(), loginUser.getLoginname(), "admin");Map<String, Object> map = new HashMap<>();map.put("token", token);map.put("role", "admin");map.put("name", loginUser.getLoginname());return new Result(true, StatusCode.OK, "登陆成功", map);}// 添加 Admin 用户@RequestMapping(value = "/add", method = RequestMethod.POST)public Result add(@RequestBody Admin admin) {adminService.add(admin);return new Result(true, StatusCode.OK, "增加成功");}
}

业务处理层 service

@Service
public class AdminService {@Autowiredprivate AdminDao adminDao;@Autowiredprivate BCryptPasswordEncoder encoder;// 根据登陆用户名和密码查询public Admin findByLoginNameAndPassword(String loginName, String password) {Admin admin = adminDao.findByLoginname(loginName);if (null != admin && encoder.matches(password, admin.getPassword())) {return admin;}return null;}// 添加管理员public void add(Admin admin) {admin.setId(UUIDUtil.getUUID());    // 主键// 密码加密String newPassword = encoder.encode(admin.getPassword());admin.setPassword(newPassword);adminDao.save(admin);}}

数据访问层 dao

public interface AdminDao extends JpaRepository<Admin, String>, JpaSpecificationExecutor<Admin> {// 管理员登陆校验Admin findByLoginname(String loginName);
}

修改UserController的delete方法

@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate HttpServletRequest servletRequest;/*** 删除:删除用户,必须拥有管理员权限,否则不能删除* <p>* 前后端约定:前端请求微服务时需要添加头信息Authorization ,内容为Bearer+空格+token** @param id*/@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)public Result delete(@PathVariable String id) {Claims claims = (Claims) servletRequest.getAttribute("admin_claims");if (null == claims) {return new Result(true, StatusCode.ACCESSERROR, "无权访问");}userService.deleteById(id);return new Result(true, StatusCode.OK, "删除成功");}}

测试生成 token 步骤
1、和上面一样使用 Postman 注册一个 Admin 账户

2、使用 Postman 模拟访问登陆,看是否返回 token

3、在使用 Bearer+空格+token,放入头部删除用户,看是否删除成功。


  1. 小知识:Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2
    的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24
    个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。JDK 中
    提供了非常方便的 BASE64Encoder 和 BASE64Decoder,用它们可以非常方便的
    完成基于 BASE64 的编码和解码。 ↩︎

密码加密与微服务鉴权JWT相关推荐

  1. BCrypt加密怎么存入数据库_第6天 密码加密与微服务鉴权JWT(下)

    上篇: Gavin:第6天 密码加密与微服务鉴权JWT(上)​zhuanlan.zhihu.com 能够使用BCrypt密码加密算法实现注册与登陆功能 能够说出常见的认证机制 能够说出JWT的组成部分 ...

  2. springcloud 微服务鉴权_springcloud 微服务权限校验JWT模式获取 token 实战(十二)...

    springcloud 微服务权限校验JWT模式获取 token 实战(十二) springcloud 微服务权限校验JWT模式获取 token 实战(十二) JWT:json web token 是 ...

  3. java 鉴权_我爱java系列之---【JWT实现微服务鉴权(一)】

    JWT介绍 JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息. 一个JWT实际上就是一个字符串,它由三部分组成,头部.载荷与签 ...

  4. springcloud 微服务鉴权_我对微服务、SpringCloud、k8s、Istio的一些杂想

    一.微服务与SOA "微服务"是一个名词,没有这个名词之前也有"微服务",一个朗朗上口的名词能让大家产生一个认知共识,这对推动一个事务的发展挺重要的,不然你叫微 ...

  5. springcloud 微服务鉴权_Java微服务框架spring cloud

    Spring Cloud是什么 Spring Boot 让我们从繁琐的配置文件中解脱了出来,而 Spring Cloud,它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发, ...

  6. JWT 实现微服务鉴权

    一.JWT JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息. 一个JWT实际上就是一个字符串,它由三部分组成,头部.载荷与签 ...

  7. 【Dubbo3高级特性】「提升系统安全性」通过令牌进行服务验证及服务鉴权控制实战指南

    系列文章目录 如果你看到了这里,那么接下来你将会认识Dubbo3的诞生将如何引领微服务领域更进一步,从而迈入云原生的领域,这当然不仅仅是Dubbo3,之前也介绍了Java生态另外一个云原生领域的技术Q ...

  8. 文件服务器鉴权,服务鉴权

    使用kmse实现服务的权限校验 通过一个简单的实例说明开发者如何通过kmse进行服务间的权限校验. 一.准备客户端和服务端两个demo 这里演示如何快速实践服务鉴权功能.假如现在有两个微服务 auth ...

  9. Spring security+jwt服务鉴权完整代码

    将代码下载到本地, 直接以包的形式添加到项目中就可以实现鉴权功能了. <!-- spring-security 和 jwt start --><dependency><g ...

最新文章

  1. 科技产品下一个重大突破将来自芯片堆叠技术
  2. Mac Eclipse上Android SDK manager闪退的问题!!
  3. DDOS SYN Flood攻击、DNS Query Flood, CC攻击简介——ddos攻击打死给钱。限网吧、黄网、博彩,,,好熟悉的感觉有木有...
  4. 杉德支付php代码实现_php实现小程序支付完整版
  5. C++11中的std::function
  6. Luhn校验原理与实现【转载】
  7. mysql 中常用的基本操作
  8. uniapp小程序的getsysteminfo_Typecho小程序:Pisces-Mini-Program
  9. 苹果首款自研芯片Mac成本可能上升 因设计改变
  10. 基于android课设报告,基于android的简单用户注册系统课程设计报告.doc
  11. 阿里视频播放vodPlayer.setMuteMode(true) 设置静音失效的解决办法
  12. 速览!PCBA需要刷三防漆,如何制作治工具?
  13. mantis修改mysql端口_Mantis配置指南
  14. GamesIndustry.biz采访尼古拉斯·弗朗西斯(Nicholas Francis)
  15. 好玩有趣的Workerman小蝌蚪匿名聊天室HTML源码
  16. 自动驾驶汽车也要驾考了,能否上路在此一举,老司机看了考试内容惊呆了!
  17. linux pptpd源码,Centos 7 源码安装pptpd
  18. java 容器排序_Java攻略第四章 容器类、排序
  19. 224、三维形体投影面积
  20. UE4 (旧版4.15-4.24)安卓打包报错license not accepted

热门文章

  1. ACM杂题——动态规划_背包问题
  2. 使用wxcharts时,当y轴值全为0时显示错误
  3. 获取基站LAC CID
  4. 网站美工之海报设计原则
  5. 【随机过程】泊松过程的协方差
  6. 对标苹果M1:高通正在研发新款ARM架构PC处理器
  7. Linux网络配置IP地址(初学Linux网络简单配置)
  8. 为什么喜欢Kindle
  9. 免费的 Docker 镜像仓库,无需注册登录
  10. ibm red hat_IBM-Red Hat对云和开发人员的意义