账号+密码+图片验证码认证
实现步骤
实现账号密码认证,执行流程如下
第一步: 对于验证码服务工程的生成验证码图片的接口在网关处需要放行,否则页面无法获取生成的验证码图片
/**=临时放行所有请求
/auth/**=认证服务地址
/content/open/**=内容管理公开访问文件接口
/media/open/**=媒资管理公开访问接口
第二步: 在认证服务工程
中定义远程调用验证码服务
的Feign接口,并在启动类上添加@EnableFeignClients
注解
- 用户输入的验证码后会连同服务端响应的
key
会一同先提交至认证服务
, 然后认证服务携带key和输入的验证码
请求验证码服务工程的校验接口
package com.xuecheng.ucenter.feignclient;
@FeignClient(value = "checkcode",fallbackFactory = CheckCodeClientFactory.class)
@RequestMapping("/checkcode")
public interface CheckCodeClient {@PostMapping(value = "/verify")public Boolean verify(@RequestParam("key") String key,@RequestParam("code") String code);}// 编写熔断降级策略
@Slf4j
@Component
public class CheckCodeClientFactory implements FallbackFactory<CheckCodeClient> {@Overridepublic CheckCodeClient create(Throwable throwable) {return new CheckCodeClient() {@Overridepublic Boolean verify(String key, String code) {log.debug("调用验证码服务熔断异常:{}", throwable.getMessage());return null;}};}
}
// 扫描交了@FeignClient注解的类
EnableFeignClients(basePackages = "com.xuecheng.ucenter.feignclient")@SpringBootApplicationpublic class AuthApplication {public static void main(String[] args) {SpringApplication.run(AuthApplication.class, args);}@BeanRestTemplate restTemplate(){RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());return restTemplate;}}
第三步: 引入Feign
在Nacos的dev环境下的配置文件
spring:application:name: auth-servicecloud:nacos:server-addr: 192.168.101.65:8848discovery:namespace: dev402group: xuecheng-plus-projectconfig:namespace: dev402group: xuecheng-plus-projectfile-extension: yamlrefresh-enabled: trueshared-configs:- data-id: swagger-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: true- data-id: logging-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: true# 引入Feign在Nacos的dev环境下的配置文件- data-id: feign-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: trueprofiles:active: dev
第四步: 在PasswordAuthServiceImpl
中增加校验验证码
和校验密码
的判断逻辑
/*** 认证Service*/
public interface AuthService {/*** 认证方法* @param authParamsDto 认证参数* @return 用户信息*/XcUserExt execute(AuthParamsDto authParamsDto);
}
@Service("password_authservice")
public class PasswordAuthServiceImpl implements AuthService {@AutowiredXcUserMapper xcUserMapper;@AutowiredPasswordEncoder passwordEncoder;@AutowiredCheckCodeClient checkCodeClient;@Overridepublic XcUserExt execute(AuthParamsDto authParamsDto) {// 获取用户提交的验证码String checkcode = authParamsDto.getCheckcode();// 获取验证码的keyString checkcodekey = authParamsDto.getCheckcodekey();if (StringUtils.isBlank(checkcode) || StringUtils.isBlank(checkcodekey)){throw new RuntimeException("验证码为空");}// 首先远程调用验证码服务工程的接口校验验证码的正确性Boolean verify = checkCodeClient.verify(checkcodekey, checkcode);// 验证码输入错误if (!verify){throw new RuntimeException("验证码输入错误");}// 验证码正确,获取登陆账号相关信息String username = authParamsDto.getUsername();// 根据账号去数据库中查询用户是否存在XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, username));// 用户不存在抛异常if (xcUser == null) {throw new RuntimeException("账号不存在");}// 用户存在,获取用户输入的密码String passwordForm = authParamsDto.getPassword();// 获取数据库中存储的用户密码String passwordDb = xcUser.getPassword();// 比较密码boolean matches = passwordEncoder.matches(passwordForm, passwordDb);// 密码不匹配抛异常if (!matches) {throw new RuntimeException("账号或密码错误");}// 密码匹配将查询得到的xcUser对象中的数据封装到扩展的xcUserExt对象中并返回XcUserExt xcUserExt = new XcUserExt();BeanUtils.copyProperties(xcUser, xcUserExt);return xcUserExt;}
}
HttpClient测试
重启认证服务,测试申请令牌接口注意JSON数据中要带上authType
# 密码模式,账号密码正确,可以成功获取authType,并正确查询到用户信息
POST localhost:53070/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username={"username":"yunqing","password":"111111","checkcode":"ZEUY","checkcodekey":"checkcodepre","authType":"password"}
前后端联调
第一步: 使用浏览器访问http://www.51xuecheng.cn/sign.html
第二步: 如果验证码和密码均正确在右上角可以看到登录的用户信息,并且前端也会把服务端响应的jwt令牌存储在cookie中,若设置不成功先清一下浏览器的缓存
自动登录
: 勾选自动登录cookie生成时间为30天,不勾选关闭浏览器窗口后就会自动删除cookie
// 在控制台手动设置cookie
document.cookie = "jwt=令牌内容"// 前端设置cookie的代码
login(){//转json串let usernameJson = JSON.stringify(this.usernamejson)console.log(usernameJson)this.formdata.username = usernameJson;let params = querystringify(this.formdata);loginSubmit(params).then(res=>{console.log(res) // 控制台输出正常console.log(res.access_token) // 控制台输出正常if(res&& res.access_token){console.log("进入到设置cookie之前") // 控制台输出正常setCookie("jwt","123",30); // 无效if(this.autologin){setCookie('jwt',res.access_token,30)}else{console.log("进入到setCookie方法上面") // 控制台输出正常setCookie('jwt',"123",0) // 无效}this.$message.success('登录成功')if(this.returnUrl){top.location=this.returnUrl}else{top.location='/'}}}).catch(error=>{if(error&&error.response&&error.response.data&&error.response.data.error_description){this.$message.error(error.response.data.error_description)}this.getCheckCode();})
}
// 对应的setCookie方法
function setCookie(name,value,Days){if(Days==0){document.cookie = name + "="+ escape (value) + ";domain=localhost;expires=0;path=/" ;}else{var exp = new Date();exp.setTime(exp.getTime() + Days*24*60*60*1000);document.cookie = name + "="+ escape (value) + ";domain=localhost;expires=" + exp.toGMTString()+";path=/";}
}
// logout()调用setCookie方法手动删除设置的jwt令牌
function logout(){setCookie('jwt','',-1)window.location='/'
}