security第十六集 引入JWT
JWT(JSON Web Token) 是一种紧凑的、自包含的、用于在各方之间安全传输信息的 JSON 对象格式。它通常用于身份认证和信息交换。
一个 JWT 由三部分组成,用 . 连接
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
────────── HEADER ────────── ─────────── PAYLOAD ──────────── ──────── SIGNATURE ────────
Header(头部)
{"alg":"HS256",// 签名算法"typ":"JWT"// Token 类型}Payload(载荷/数据体)
{// 标准字段(Registered Claims)"iss":"auth-server",// 签发者"sub":"1234567890",// 主题(通常是用户ID)"aud":"order-service",// 受众"exp":1735689600,// 过期时间"iat":1735686000,// 签发时间// 自定义字段(Public/Private Claims)"userId":"user-001","username":"zhangsan","role":"ADMIN","permissions":["read","write"]}Signature(签名)
```javaHMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)JWT工作原理
简单了解 jwt 之后 ,我们根据上两集的多因素认证的 认证服务和客户端进行 一下 改造
- 首先修改一下认证服务
1.加入jwt所需maven依赖
<!--JWT依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope></dependency>2.创建jwtUtil
@ComponentpublicclassJwtUtil{//这个需自己定义,我用的 HS256,所以密钥至少要 32 个字符privatestaticfinalStringSECRET_KEY="a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6";privatestaticfinallongEXPIRATION_TIME=24*60*60*1000;// 24小时privateSecretKeygetSigningKey(){returnKeys.hmacShaKeyFor(SECRET_KEY.getBytes());}/** * 生成 JWT Token */publicStringgenerateToken(Stringusername,StringuserId){Map<String,Object>claims=newHashMap<>();claims.put("userId",userId);claims.put("username",username);returnJwts.builder().setClaims(claims).setSubject(username).setIssuedAt(newDate()).setExpiration(newDate(System.currentTimeMillis()+EXPIRATION_TIME)).signWith(getSigningKey(),SignatureAlgorithm.HS256).compact();}/** * 验证 JWT Token */publicbooleanvalidateToken(Stringtoken){try{Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token);returntrue;}catch(JwtException|IllegalArgumentExceptione){returnfalse;}}/** * 从 Token 中获取用户名 */publicStringgetUsernameFromToken(Stringtoken){Claimsclaims=Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();returnclaims.getSubject();}/** * 从 Token 中获取用户ID */publicStringgetUserIdFromToken(Stringtoken){Claimsclaims=Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();returnclaims.get("userId",String.class);}}3.修改UserService中的userAuth 方法,返回jwt
//验证密码是否匹配if(passwordEncoder.matches(user.getPassword(),u.getPassword())){//密码匹配后生成 JWT TokenStringtoken=jwtUtil.generateToken(u.getUsername(),u.getId());returntoken;}else{thrownewBadCredentialsException("Bad credentials");}4.修改AuthController的/user/auth方法返回token
@PostMapping("/user/auth")publicMap<String,String>userAuth(@RequestBodyUseruser){Stringtoken=userService.userAuth(user);Map<String,String>map=newHashMap<>();map.put("token",token);returnmap;}- 然后我们修改business-service
1.pom文件引入上面的jwt依赖
2.创建JwtUtil和上诉一样
3.修改AuthenticationServerFacade的checkPassword方法,获取返回token
publicStringcheckPassword(Stringusername,Stringpassword){Stringurl=baseUrl+"/user/auth";Useruser=newUser();user.setUsername(username);user.setPassword(password);HttpEntity<User>request=newHttpEntity<>(user);ResponseEntity<Map<String,String>>response=restTemplate.exchange(url,HttpMethod.POST,request,newParameterizedTypeReference<Map<String,String>>(){});returnresponse.getBody().get("token");}4.创建JwtAuthenticationFilter来验证jwt
@ComponentpublicclassJwtAuthenticationFilterextendsOncePerRequestFilter{@AutowiredprivateJwtUtiljwtUtil;@OverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain)throwsServletException,IOException{Stringheader=request.getHeader("Authorization");if(header!=null&&header.startsWith("Bearer ")){Stringtoken=header.substring(7);if(jwtUtil.validateToken(token)){Stringusername=jwtUtil.getUsernameFromToken(token);Stringuserid=jwtUtil.getUserIdFromToken(token);UsernamePasswordAuthenticationTokenauthentication=newUsernamePasswordAuthenticationToken(username,null,Collections.emptyList());authentication.setDetails(userid);SecurityContextHolder.getContext().setAuthentication(authentication);}}filterChain.doFilter(request,response);}@OverrideprotectedbooleanshouldNotFilter(HttpServletRequestrequest){returnrequest.getServletPath().equals("/login");}}5.创建LoginController实现/login方法
@RestControllerpublicclassLoginController{@AutowiredprivateAuthenticationServerFacadeauthenticationServerFacade;/** * 登录接口 - 门面模式 * 客户端只和 Business-Service 交互 */@PostMapping("/login")publicResponseEntity<Map<String,String>>login(@RequestBodyMap<String,String>credentials){Stringusername=credentials.get("username");Stringpassword=credentials.get("password");// 内部调用 Security-Service 获取 JWT TokenStringtoken=authenticationServerFacade.checkPassword(username,password);Map<String,String>response=newHashMap<>();response.put("token",token);returnResponseEntity.ok(response);}}6.修改 SecurityConfig类的 configure(HttpSecurity http)方法,引入jwt过滤器并放行/login
@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.csrf().disable();http.addFilterBefore(jwtAuthenticationFilter,BasicAuthenticationFilter.class);http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated();}好,现在我们可以测试访问了,我的 business-service端口号是8124
可以看到返回信息中已经有了 token,这个就是JWT
当我们再次访问business-service中的其他业务时就可以携带这个token值去访问
假设我们在business-service中在创建一个 controller来测试一下
@RestControllerpublicclassUserController{@AutowiredprivateAuthenticationServerFacadeauthenticationServerFacade;@GetMapping("/getUserInfoByToken")publicJSONObjectgetOrders(){Stringusername=(String)SecurityContextHolder.getContext().getAuthentication().getPrincipal();StringuserId=(String)SecurityContextHolder.getContext().getAuthentication().getDetails();JSONObjectresult=newJSONObject();result.put("username",username);result.put("userId",userId);result.put("message","获取用户信息成功");returnresult;}}现在我们用postman测试一下这个接口,注意Authorization中要选择 BearerToken,并传入刚才生成的token值
看结果,已经能把jwt中的userId和username解析出来了
如果要是想更完善一些,可以在认证服务中 创建返回用户信息接口/user/info
@RestControllerpublicclassAuthController{@AutowiredprivateUserServiceuserService;//基于用户名和密码的认证,返回token@PostMapping("/user/auth")publicMap<String,String>userAuth(@RequestBodyUseruser){Stringtoken=userService.userAuth(user);Map<String,String>map=newHashMap<>();map.put("token",token);returnmap;}@GetMapping("/user/info")publicUserDTOgetUserInfo(@RequestParamStringusername){returnuserService.getUserByUsername(username);}}然后再 business-service中的UserController中添加 getUserInfo方法
@GetMapping("/getUserInfo")publicUsergetUserInfo(){Stringusername=(String)SecurityContextHolder.getContext().getAuthentication().getPrincipal();Useruser=authenticationServerFacade.getUserInfo(username);returnuser;}在AuthenticationServerFacade类中添加对应方法
/** * 调用认证中心获取用户详细信息 */publicUsergetUserInfo(Stringusername){Stringurl=baseUrl+"/user/info?username="+username;ResponseEntity<User>response=restTemplate.getForEntity(url,User.class);returnresponse.getBody();}此时我们用postman在测试一下
可以看到 返回的user用户信息已经和 上两集的 数据信息完全对应上了!
至此jwt已经引入完毕
