账号+密码+图片验证码认证
实现步骤
实现账号密码认证,执行流程如下
第一步: 对于验证码服务工程的生成验证码图片的接口在网关处需要放行,否则页面无法获取生成的验证码图片
/**=临时放行所有请求
/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> {
@Override
public CheckCodeClient create(Throwable throwable) {
return new CheckCodeClient() {
@Override
public Boolean verify(String key, String code) {
log.debug("调用验证码服务熔断异常:{}", throwable.getMessage());
return null;
}
};
}
}
// 扫描交了@FeignClient注解的类
EnableFeignClients(basePackages = "com.xuecheng.ucenter.feignclient")
@SpringBootApplication
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
@Bean
RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
return restTemplate;
}
}
第三步: 引入Feign
在Nacos的dev环境下的配置文件
spring:
application:
name: auth-service
cloud:
nacos:
server-addr: 192.168.101.65:8848
discovery:
namespace: dev402
group: xuecheng-plus-project
config:
namespace: dev402
group: xuecheng-plus-project
file-extension: yaml
refresh-enabled: true
shared-configs:
- data-id: swagger-${spring.profiles.active}.yaml
group: xuecheng-plus-common
refresh: true
- data-id: logging-${spring.profiles.active}.yaml
group: xuecheng-plus-common
refresh: true
# 引入Feign在Nacos的dev环境下的配置文件
- data-id: feign-${spring.profiles.active}.yaml
group: xuecheng-plus-common
refresh: true
profiles:
active: dev
第四步: 在PasswordAuthServiceImpl
中增加校验验证码
和校验密码
的判断逻辑
/**
* 认证Service
*/
public interface AuthService {
/**
* 认证方法
* @param authParamsDto 认证参数
* @return 用户信息
*/
XcUserExt execute(AuthParamsDto authParamsDto);
}
@Service("password_authservice")
public class PasswordAuthServiceImpl implements AuthService {
@Autowired
XcUserMapper xcUserMapper;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
CheckCodeClient checkCodeClient;
@Override
public XcUserExt execute(AuthParamsDto authParamsDto) {
// 获取用户提交的验证码
String checkcode = authParamsDto.getCheckcode();
// 获取验证码的key
String 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='/'
}