赞
踩
文件位置:com.ruoyi.web.controller.common.CaptchaController
/** * 生成验证码 */ @GetMapping("/captchaImage") public AjaxResult getCode(HttpServletResponse response) throws IOException { //生成一个Ajax 对象,后端与前端交互都是用Ajax AjaxResult ajax = AjaxResult.success(); //确定是否开启验证码功能,开启则进入验证码校验,不开启则直接返回Ajax boolean captchaOnOff = configService.selectCaptchaOnOff(); ajax.put("captchaOnOff", captchaOnOff); if (!captchaOnOff) { return ajax; } // 保存验证码信息 //先生成一个uuid,再加上头,就生成了存在redis里面的键值key,组合拼接结果就是:举例:captcha_codes:a9291c53cb99428c81e4e344544877ac String uuid = IdUtils.simpleUUID(); String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; String capStr = null, code = null; BufferedImage image = null; // 生成验证码 //判断是不是数字类型验证码 String captchaType = RuoYiConfig.getCaptchaType(); if ("math".equals(captchaType)) { //谷歌的验证码文本创建(展示问题@结果)3-1=?@2 String capText = captchaProducerMath.createText(); //问题 capStr = capText.substring(0, capText.lastIndexOf("@")); //答案 code = capText.substring(capText.lastIndexOf("@") + 1); //根据问题创建图片 image = captchaProducerMath.createImage(capStr); } else if ("char".equals(captchaType)) { //生成文本类的验证码 capStr = code = captchaProducer.createText(); //创建验证码对应图片 image = captchaProducer.createImage(capStr); } /**设置验证码缓存 * set key、code、验证码有效期(分钟)、时间颗粒度,若依默认是2min */ redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try { //使用图像编写器(支持给定格式【渲染图像】)将图片写入输出流 ImageIO.write(image, "jpg", os); } catch (IOException e) { return AjaxResult.error(e.getMessage()); } ajax.put("uuid", uuid); //输出流转换为Base64 ajax.put("img", Base64.encode(os.toByteArray())); return ajax; }
可以设置application.yml 文件修改captchaType为char 可以使用char验证码
文件位置:com.ruoyi.web.controller.system.SysLoginController
前端验证码验证成功时,会返回刚刚创建的uuid,登录时,会携带刚刚返回的code和uuid和输入的username 和 password
进入后端操作逻辑,访问的是/login 接口
/** * 登录方法 * * @param loginBody 登录信息 * @return 结果 */ @PostMapping("/login") public AjaxResult login(@RequestBody LoginBody loginBody) { /** * 之前生成验证码的时候给了成功返回结果+uuid+图片 * 现在请求对象loginBody把uuid放参数中带过来 */ AjaxResult ajax = AjaxResult.success();// 先准备一个成功返回{msg=操作成功, code=200} // 生成令牌 //login()验证码校验、用户验证、登录日志、生成token String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid()); ajax.put(Constants.TOKEN, token);// 通过使用hashMap的方法给当前的AjaxResult对象,添加一个元素与值 // {msg, code, token} return ajax; }
进入这个login() 校验方法
/** * 登录验证 * * @param username 用户名 * @param password 密码 * @param code 验证码 * @param uuid 唯一标识 * @return 结果 */ public String login(String username, String password, String code, String uuid) { boolean captchaOnOff = configService.selectCaptchaOnOff(); // 验证码开关 if (captchaOnOff) { //校验验证码===>*********看下一个方法******* validateCaptcha(username, code, uuid); } // 用户验证 Authentication authentication = null; try { // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(username, password)); //UsernamePasswordAuthenticationToken [Principal=com.ruoyi.common.core.domain.model.LoginUser@748cc4df, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]] } catch (Exception e) { if (e instanceof BadCredentialsException) { //多线程操作,日志记录登录信息,登陆不成功:user.password.not.match AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } else { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new ServiceException(e.getMessage()); } } //多线程操作,日志记录登录信息,登陆成功:user.login.success AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal();//com.ruoyi.common.core.domain.model.LoginUser@748cc4df //记录登录信息=====修改用户基本信息(最后IP、时间) recordLoginInfo(loginUser.getUserId()); // 生成token return tokenService.createToken(loginUser); }
进入这个validateCaptcha(username, code, uuid); 校验方法
/** * 校验验证码 * * @param username 用户名 * @param code 验证码 * @param uuid 唯一标识 * @return 结果 */ public void validateCaptcha(String username, String code, String uuid) { String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;//验证码 redis key+uuid String captcha = redisCache.getCacheObject(verifyKey);//获取到redis中对应的验证码 redisCache.deleteObject(verifyKey);//删除掉这条验证码 if (captcha == null) { //验证码不存在 //异步的执行任务:recordLogininfor登录信息记录 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); throw new CaptchaExpireException(); } if (!code.equalsIgnoreCase(captcha)) { //code不等于验证码 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new CaptchaException(); } }
进入这个createToken(loginUser) 生成方法
Token生成流程:
首先是创建Token,用一个UUID工具类创建了一个随机的uuid,保存到LoginUser对象中。这个LoginUser实现了Spring Security中的UserDetails接口,用于保存登录用户信息。
然后有一个setUserAgent ()方法设置用户代理信息,包括ip地址、登陆地点、浏览器类型和操作系统。
接着有一个refreshToken ()方法设置令牌有效期,可以设置用户的登录时间,token过期时间(默认设置30min)以及登录用户信息缓存到Redis中。
最后生成jwt令牌,该令牌保存了token信息,签名算法是HS512,密钥是在配置文件中配置的。
/** * 创建令牌 * * @param loginUser 用户信息 * @return 令牌 */ public String createToken(LoginUser loginUser) { String token = IdUtils.fastUUID(); //设置token loginUser.setToken(token); //设置用户代理信息 setUserAgent(loginUser); //刷新令牌有效期 refreshToken(loginUser); Map<String, Object> claims = new HashMap<>(); claims.put(Constants.LOGIN_USER_KEY, token); // claims这里只是令牌前缀 return createToken(claims); } //两个方法这里参数不同参数不同 /** * 从数据声明生成令牌 * * @param claims 数据声明 * @return 令牌 */ private String createToken(Map<String, Object> claims) { //JSON Web Token (JWT)就是一种Token的编码算法 String token = Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, secret).compact(); //至此,token生成 return token; }
文件位置:com.ruoyi.web.controller.system.SysLoginController
/** * token过滤器 验证token有效性 * 每个请求过滤器一次OncePerRequestFilter * @author ruoyi */ @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private TokenService tokenService; //做内部过滤 @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { LoginUser loginUser = tokenService.getLoginUser(request); /** * 判断用户存在 和 用户已登录(认证) */ if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) { /** * token是否过期,相差不足20分钟,自动刷新缓存 */ tokenService.verifyToken(loginUser); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); //上下文设置新的认证 SecurityContextHolder.getContext().setAuthentication(authenticationToken); } /** * 导致调用链中的下一个过滤器,或者如果调用 过滤器是链中的最后一个过滤器,则导致调用链末尾的资源。 */ chain.doFilter(request, response); } }
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。