SpringBoot中使用JWT的实战
作者:Java Fans
JWT 简介
JWT(JSON Web Token)是一种用于身份验证和授权的开放标准(RFC 7519),它使用JSON格式传输信息,可以在不同系统之间安全地传递数据。JWT由三部分组成:头部、载荷和签名。头部包含算法和类型信息,载荷包含用户信息和其他元数据,签名用于验证JWT的真实性和完整性。JWT的优点包括可扩展性、跨平台、无状态和安全性高等。它被广泛应用于Web应用程序、移动应用程序和API等领域。
JWT 身份认证流程
- 客户端向服务器发送用户名和密码,通常使用POST请求方式,将用户名和密码作为请求体发送给服务器。
- 服务器验证用户名和密码的正确性,如果验证通过,生成一个JWT令牌,并将令牌返回给客户端。JWT令牌包括三部分:头部、载荷和签名。头部包含令牌类型和加密算法,载荷包含用户信息和过期时间等信息,签名用于验证令牌的真实性。
- 客户端将JWT令牌保存在本地,通常使用localStorage或sessionStorage等方式保存。
- 客户端向服务器发送请求,请求头部包含JWT令牌。通常使用Authorization头部字段,格式为Bearer <token>,其中<token>为JWT令牌。
- 服务器验证JWT令牌的真实性,通常使用JWT库进行验证。验证过程包括以下步骤:解析JWT令牌,验证头部和载荷的签名是否正确,验证令牌是否过期,验证令牌是否被篡改等。如果验证通过,返回请求结果;否则返回错误信息。
案例分享
下面是一个使用Spring Boot和JWT进行身份认证的示例:
1、后端代码
1.1 添加依赖
在pom.xml文件中添加以下依赖:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0.1</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.1</version> </dependency>
1.2 添加 ShiroConfig 配置类
添加一个 ShiroConfig 类,用于配置Shiro框架的安全管理器和过滤器。
其中包含了三个方法:shiroFilterFactoryBean、defaultWebSecurityManager和realm。
- shiroFilterFactoryBean方法用于配置Shiro的过滤器,包括设置拦截规则、放行请求和设置默认的登录页等。
- defaultWebSecurityManager方法用于配置Shiro的安全管理器,包括设置记住我功能和将自定义域对象交给Spring管理等。
- realm方法用于创建自定义域对象,包括设置凭证匹配器、开启缓存和将认证和授权缓存写入Redis等。
整个类的作用是为Shiro框架提供安全管理和过滤器的配置。
代码如下:
package zk.gch.temperature.shiro; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import zk.gch.temperature.filter.JwtAuthenticationFilter; import java.util.LinkedHashMap; import javax.servlet.Filter; import java.util.HashMap; import java.util.Map; @Configuration public class ShiroConfig { // shiro中的过滤器 交给spring容器管理 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { System.out.println("securityManager = " + securityManager); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //配置拦截的规则 Map<String, Filter> filters = new HashMap<>(); filters.put("jwt", new JwtAuthenticationFilter()); shiroFilterFactoryBean.setFilters(filters); LinkedHashMap<String, String> map = new LinkedHashMap<>(); //放行登录请求 anon 可匿名访问 map.put("/user/login", "anon"); map.put("/user/add", "anon"); map.put("/register.html", "anon"); // 放行静态资源 map.put("/dist/**", "anon"); // 放行验证码请求 map.put("/captcha/getCaptcha", "anon"); //已登录或“记住我”的用户才能访问 map.put("/**", "user"); //放行所有携带token请求的访问 map.put("/**", "jwt"); // 设置默认的登录页 shiroFilterFactoryBean.setLoginUrl("/login.html"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } // 将安全管理器交由spring管理 @Bean public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); // 设置一周免登录 CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); SimpleCookie rmbme = new SimpleCookie("rmbme"); rmbme.setMaxAge(60*60*24*7); cookieRememberMeManager.setCookie(rmbme); defaultWebSecurityManager.setRememberMeManager(cookieRememberMeManager); defaultWebSecurityManager.setRealm(realm); return defaultWebSecurityManager; } // 将自定义域对象 交给spring管理 @Bean public Realm realm() { CustomerRealm customerRealm = new CustomerRealm(); //设置凭证匹配器 MD5 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher("MD5"); hashedCredentialsMatcher.setHashIterations(20); customerRealm.setCredentialsMatcher(hashedCredentialsMatcher); // 开启shiro的缓存 开启全局缓存 customerRealm.setCachingEnabled(true); //将认证和授权缓存写入redis 分布式缓存 customerRealm.setCacheManager(new RedisCacheManager()); // 设置认证缓存 customerRealm.setAuthenticationCachingEnabled(true); customerRealm.setAuthenticationCacheName("authentication"); // 设置授权缓存 customerRealm.setAuthorizationCachingEnabled(true); customerRealm.setAuthorizationCacheName("authorization"); return customerRealm; } }
1.3 添加 JwtUtil 工具类
JwtUtil 工具类是一个用于生成和解析JWT(JSON Web Token)的工具类。JWT是一种用于身份验证和授权的开放标准,它可以在客户端和服务器之间传递安全可靠的信息。
该工具类中包含了生成JWT的方法createJWT(),可以设置token中要存放的数据、过期时间等信息,并使用HS256对称加密算法签名。同时,该工具类还包含了解析JWT的方法parseJWT(),可以解析出token中存放的数据。此外,该工具类还包含了一些常量和辅助方法,如JWT_TTL、JWT_KEY、getUUID()等。
代码如下:
package zk.gch.temperature.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.UUID; @Component public class JwtUtil { //有效期为 public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时 //设置秘钥明文 public static final String JWT_KEY = "sangeng"; public static String getUUID(){ String token = UUID.randomUUID().toString().replaceAll("-", ""); return token; } /** * 生成jtw * @param subject token中要存放的数据(json格式) * @return */ public static String createJWT(String subject) { JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间 return builder.compact(); } /** * 生成jtw * @param subject token中要存放的数据(json格式) * @param ttlMillis token超时时间 * @return */ public static String createJWT(String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间 return builder.compact(); } private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if(ttlMillis==null){ ttlMillis=JwtUtil.JWT_TTL; } long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) //唯一的ID .setSubject(subject) // 主题 可以是JSON数据 .setIssuer("sg") // 签发者 .setIssuedAt(now) // 签发时间 .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥 .setExpiration(expDate); } /** * 创建token * @param id * @param subject * @param ttlMillis * @return */ public static String createJWT(String id, String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间 return builder.compact(); } public static void main(String[] args) throws Exception { String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg"; Claims claims = parseJWT(token); System.out.println(claims); } /** * 生成加密后的秘钥 secretKey * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * 解析 * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); } }
1.4 添加 JwtAuthenticationFilter 过滤器类
JwtAuthenticationFilter 类是一个基于JWT(JSON Web Token)的身份验证过滤器,用于在每个请求中验证用户的身份。它首先从请求头中获取Authorization字段,然后解析其中的token并验证其有效性。如果token有效,则将用户信息存入SecurityContextHolder中,以便后续的身份验证。如果token无效,则返回401 Unauthorized。最后,它放行请求,使其继续处理。
代码如下:
package zk.gch.temperature.filter; import io.jsonwebtoken.Claims; import org.apache.shiro.web.servlet.OncePerRequestFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import zk.gch.temperature.utils.JwtUtil; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Override protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 获取请求头中的Authorization字段 String header = httpRequest.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { // 获取token String token = header.substring(7); System.out.println("token = " + token); try { // 解析token并验证其有效性 Claims claims = jwtUtil.parseJWT(token); if (claims != null) { // 将用户信息存入SecurityContextHolder中 // UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(claims.getSubject(), null, null); // SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { // 验证失败,返回401 Unauthorized httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } } // 放行请求 filterChain.doFilter(request, response); } }
1.5 添加 UserController 控制器类
主要代码如下:
将JwtUtil添加到spring容器管理:
@Autowired private JwtUtil jwtUtil;
请求成功后,将token作为返回值,返回给前端:
String token = jwtUtil.createJWT(user.getId().toString(), JSON.toJSONString(user), JwtUtil.JWT_TTL);
全部代码如下:
package zk.gch.temperature.controller; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; import zk.gch.temperature.commons.CodeMsg; import zk.gch.temperature.commons.ResponseResult; import zk.gch.temperature.dto.OhterPageDTO; import zk.gch.temperature.dto.UserLoginDTO; import zk.gch.temperature.dto.UserPageDTO; import zk.gch.temperature.dto.UserRegisterDTO; import zk.gch.temperature.entity.BasicInfo; import zk.gch.temperature.entity.Device; import zk.gch.temperature.entity.User; import zk.gch.temperature.service.UserService; import zk.gch.temperature.utils.JwtUtil; import javax.servlet.http.HttpSession; import java.util.Arrays; import java.util.List; @RestController @Api(tags="主用户模块") @RequestMapping("user") public class UserController { @Autowired private UserService userService; @Autowired private RedisTemplate redisTemplate; @Autowired private JwtUtil jwtUtil; @ApiOperation("用户注册") @PostMapping("add") public ResponseResult add(@RequestBody UserRegisterDTO user){ return userService.saveUser(user); } @ApiOperation("用户登录") @GetMapping("login") public ResponseResult login(UserLoginDTO userLoginDTO, HttpSession session){ System.out.println("userLoginDTO.getSessionId() = " + userLoginDTO.getSessionId()); String sessionId=""; // 1.判定用户的验证码是否正确 if(userLoginDTO.getSessionId()!=null){ sessionId=userLoginDTO.getSessionId(); }else{ sessionId=session.getId(); } String code = (String) redisTemplate.opsForValue().get(sessionId); System.out.println("code = " + code); String captcha = userLoginDTO.getCaptcha(); System.out.println("captcha = " + captcha); if(code==null){ // 验证码失效 return new ResponseResult(CodeMsg.CAPTCHA_EXPIRE); }else{ if (code.equals(captcha)){ // 验证码正确 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userLoginDTO.getName(), userLoginDTO.getPassword()); // 判定用户是否开启 免登录 String rememberMe = userLoginDTO.getRememberMe(); if("true".equals(rememberMe)){ usernamePasswordToken.setRememberMe(true); } subject.login(usernamePasswordToken); QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.eq("name",userLoginDTO.getName()); User user = userService.getOne(userQueryWrapper); if(!userLoginDTO.getRole().equals(user.getRole())){//角色错误 return new ResponseResult(CodeMsg.ROLE_ERROR); } String token = jwtUtil.createJWT(user.getId().toString(), JSON.toJSONString(user), JwtUtil.JWT_TTL); return new ResponseResult(CodeMsg.SUCCESS,null,token); }else { // 验证码错误 return new ResponseResult(CodeMsg.CAPTCHA_ERROR); } } } @ApiOperation("用户退出") @GetMapping("logout") public ResponseResult logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); return new ResponseResult(CodeMsg.SUCCESS); } @ApiOperation("查询用户信息列表") @GetMapping("all") public ResponseResult selectPage(UserPageDTO userPageDTO){ return userService.selectPage(userPageDTO); } @ApiOperation(value = "根据用户名查询单个用户信息") @GetMapping("getByName") public ResponseResult getByName(String name){ LambdaQueryWrapper<User> lambda = new QueryWrapper<User>().lambda(); lambda.eq((name!=null&&!"".equals(name)),User::getName,name); User user = userService.getOne(lambda); return new ResponseResult(CodeMsg.SUCCESS,null,user); } @ApiOperation(value = "根据id查询单个用户信息") @GetMapping("getById") public ResponseResult getById(Integer id){ User user = userService.getById(id); return new ResponseResult(CodeMsg.SUCCESS,null,user); } @ApiOperation("更新用户信息(绑定设备)") @PutMapping("update") public ResponseResult update(@RequestBody User user){ userService.updateById(user); return new ResponseResult(CodeMsg.SUCCESS); } @ApiOperation("删除用户") @DeleteMapping("delete") public ResponseResult delete(Integer[] ids){ List<Integer> integers = Arrays.asList(ids); userService.removeBatchByIds(integers); return new ResponseResult(CodeMsg.SUCCESS); } //密码重置 @ApiOperation("密码重置") @PostMapping("/updatePwd") public ResponseResult updatePwd(Integer id){ return userService.updatePwd(id); } }
2、前端代码
登陆成功的代码如下:
$.get("/user/login",data,function (res) { if(res.code==0){ layer.msg(res.msg,{icon:1},function () { localStorage.setItem("token",res.data); window.location = 'index.html'; }) }else{ layer.msg(res.msg) } },"JSON")
主要就是将token存储在localStorage中;
其他页面请求接口时,在请求头中添加Authorization字段;代码如下:
var token = localStorage.getItem("token"); table.render({ elem: '#currentTableId', url: '/other/all?userName=' + localStorage.getItem("name"), toolbar: '#toolbarDemo', beforeSend: function(xhr) { // 在请求头中添加Authorization字段 xhr.setRequestHeader("Authorization", "Bearer " + token); }, cols: [[ {type: "checkbox", width: 50}, {field: 'id', title: 'ID', width: 100, sort: true, hide: true}, {field: 'name', title: '用户名', width: 100}, {field: 'age', title: '年龄(周岁)', width: 100}, {field: 'height', title: '身高(cm)', width: 100}, {field: 'weight', title: '体重(kg)', width: 100}, {field: 'maxTem', title: '体温最大值(℃)', width: 130}, {field: 'minTem', title: '体温最小值(℃)', width: 130}, {field: 'tel', title: '联系方式', Width: 100}, {field: 'userName', title: '主用户名', hide: true}, {field: 'deviceId', title: '设备编号', width: 100}, {field: 'deviceState', title: '设备状态', templet: '#stateSwitch'}, {field: 'createTime', title: '创建时间'}, {field: 'updateTime', title: '修改时间'}, {fixed: 'right', title: '操作', width: 134, minWidth: 125, toolbar: '#barDemo'} ]], limits: [5, 10, 15, 20, 25, 50], limit: 5, page: true, skin: 'line' });
主要代码:
获取token:var token = localStorage.getItem("token");
在请求头中添加Authorization字段:
beforeSend: function(xhr) { // 在请求头中添加Authorization字段 xhr.setRequestHeader("Authorization", "Bearer " + token); },
到此这篇关于SpringBoot中使用JWT的实战的文章就介绍到这了,更多相关SpringBoot使用JWT内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!