代码链接:
后端java代码
前端vue代码
前端vue登录页面
<template>
<div>
<el-form
:rules="rules"
ref="loginForm"
v-loading="loading"
element-loading-text="正在登录..."
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
:model="loginForm"
class="loginContainer">
<h3 class="loginTitle">登录</h3>
<div v-show="isLoginByPassword">
<el-form-item prop="username">
<el-input size="normal" type="text" v-model="loginForm.userName" auto-complete="off" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input size="normal" type="password" v-model="loginForm.password" auto-complete="off" placeholder="请输入密码" @keydown.enter.native="submitLogin"></el-input>
</el-form-item>
<el-checkbox style="float:left;" size="normal" class="loginRemember" v-model="checked">记住我</el-checkbox>
</div>
<div v-show="!isLoginByPassword">
<el-form-item prop="phoneNum">
<el-input size="normal" type="text" v-model="loginForm.phoneNum" auto-complete="off" placeholder="请输入手机号码"></el-input>
</el-form-item>
<el-form-item prop="verificationCode">
<el-input size="normal" type="text" v-model="loginForm.verificationCode" auto-complete="off" placeholder="请输入验证码" @keydown.enter.native="submitLogin">
<el-button slot="append" :class="buttonHasGotCode" @click="getVerificationCode" :disabled="hasGotCode">{{codeMsg}}</el-button>
</el-input>
</el-form-item>
<el-checkbox style="float:left;" size="normal" class="loginRemember" v-model="checked">记住我</el-checkbox>
</div>
<el-button size="normal" type="primary" style="width: 100%;" @click="submitLogin" >登录</el-button>
<div class="otheroperation" >
<el-link style="float:left;" target="_blank" v-show="isLoginByPassword" @click="changeLoginWay">短信验证码登录</el-link>
<el-link style="float:left;" target="_blank" v-show="!isLoginByPassword" @click="changeLoginWay">账号密码登录</el-link>
<el-link style="float:right;" href="" target="_blank">注册</el-link>
</div>
</el-form>
</div>
</template>
<script >
export default {
name: "Login",
data() {
return {
loading: false,
loginForm: {
phoneNum:'',
verificationCode:'',
userName: '',
password: ''
},
checked: true,
passwordRules: {
userName: [{required: true, message: '请输入用户名', trigger: 'blur'}],
password: [{required: true, message: '请输入密码', trigger: 'blur'}]
},
phoneRules: {
phoneNum: [{required: true, message: '请输入手机号码', trigger: 'blur'}],
verificationCode: [{required: true, message: '请输入验证码', trigger: 'blur'}]
},
isLoginByPassword: false,
codeMsg: '获取验证码',
time:0,
hasGotCode: false,
buttonHasGotCode: 'verificationCodeButton'
}
},
computed: {
rules(){
if(this.isLoginByPassword){
return this.passwordRules;
} else {
return this.phoneRules;
}
},
},
methods: {
//更换登录方式
changeLoginWay(){
this.isLoginByPassword = !this.isLoginByPassword
},
//成功后进行跳转
loginSuccess(resp){
this.loading = false;
if (resp) {
this.$store.dispatch('saveSession', resp.session);//存储session
this.$router.replace('/');
}
},
//登录
submitLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true;
//账号密码登录
if(this.isLoginByPassword){
this.postRequest('/login', this.loginForm).then(resp => this.loginSuccess(resp))
.catch((e) => {
console.log('error' + e);
});
} else {
//手机登录
this.postRequest('/loginByPhoneNum', this.loginForm).then(resp => this.loginSuccess(resp))
.catch((e) => {
console.log('error' + e);
});;
}
} else {
this.$message.error('请输入所有字段');
return false;
}
});
},
//获取验证码
getVerificationCode() {
if(this.checkPhoneNum(this.loginForm.phoneNum)){
this.postRequest('/getVerificationCode', this.loginForm).then(resp => {
if (resp) {
this.changeCodeMsg ();
}
})
}else{
this.$message.error("手机号码有误,请重填");
}
},
//验证手机号码
checkPhoneNum (phoneNum) {
if(!(/^1[3456789]\d{9}$/.test(phoneNum))){
return false;
} else {
return true;
}
},
//已获取验证码进行倒计时,60秒后重新获取
changeCodeMsg () {
this.time = 60;
this.hasGotCode = true;
this.buttonHasGotCode = 'verificationCodeButtonDisabled';
this.timer = setInterval(() =>{
this.codeMsg = '('+this.time+')'+'已获取验证码';
if(this.time === 0){
clearInterval(this.timer);//销毁计时器
this.buttonHasGotCode = 'verificationCodeButton';
this.codeMsg = '重新获取验证码';
this.hasGotCode = false;
}
this.time --;
}, 1000);
}
}
}
</script>
<style>
//获取验证码按钮样式
.verificationCodeButton {
width: 140px;
color: #FFF!important;
background-color: #409EFF!important;
}
略
</style>
tip:1.可以用计算属性实现表单的分组校验
computed: {
rules(){
if(this.isLoginByPassword){
return this.passwordRules;
} else {
return this.phoneRules;
}
},
},
data() {
return {
passwordRules: {
userName: [{required: true, message: '请输入用户名', trigger: 'blur'}],
password: [{required: true, message: '请输入密码', trigger: 'blur'}]
},
phoneRules: {
phoneNum: [{required: true, message: '请输入手机号码', trigger: 'blur'}],
verificationCode: [{required: true, message: '请输入验证码', trigger: 'blur'}]
},
}
},
tip2:使用eleUI时,在style编写的样式优先级别不够高会被默认样式覆盖,因此需要加上!import提高权重。
<style>
//获取验证码按钮样式
.verificationCodeButton {
width: 140px;
color: #FFF!important;
background-color: #409EFF!important;
}
略
</style>
tip3:methods内函数互相调用记得加this.!!!,否则会报not defined未定义的错
java后端:
账号密码登录相关的坑之前博文已经写过了,这里不细讲了。密码验证由于shiro自己验证的,手机验证码登录如何绕过这一块看了网上的一些方案感觉都不是很满意,然后自己想了两个方案。
方案一:用户获取验证码时生成一个随机验证码,发送验证并缓存到redis,然后登录的时候自己校验验证码,如果一致的话就从数据里通过手机号拿出用户名密码手动登录。。。当然,这个方案比较偷懒,用起来总感觉怪怪的。
方案二:验证的时候把token里用户名和密码直接替换成从前端传来的手机号和验证码,同样在ShiroRealm里获取authenticationInfo的时候也将密码换成从缓存获取的验证码,然后校验验证码就交给Shiro了
@RestController
public class PhoneController {
@Resource
private VerificationCodeService verificationCodeService;
@Resource
CacheManager cacheManager;
@PostMapping("/getVerificationCode")
public Map<String, Object> buildVerificationCode(@RequestBody Map<String, String> phone){
Map<String, Object> map = new HashMap<String, Object>();
String code = verificationCodeService.buildVerificationCode(phone.get("phoneNum"));
if(null != code){
if(null != verificationCodeService.getVerificationCode(phone.get("phoneNum"))) {//验证是否有存进缓存
map.put("code", "200");
map.put("msg", "验证码发送成功");
return map;
// }
}
}
map.put("code","401");
map.put("msg","验证码发送失败");
return map;
}
@Service
public class VerificationCodeServiceImpl implements VerificationCodeService {
@Autowired
private UserService userService;
@Resource
CacheManager cacheManager;
@Override
@CachePut(value = "verificationCode")//用户点击获取验证码@CachePut 每次都重新缓存,每次生成的验证码不一样 ,如果每次生成的验证码一样则用@Cacheable,下次查询直接查询缓存
public String buildVerificationCode(String phoneNum) {
User user = userService.getUserByPhoneNum(phoneNum);
if(null == user){
return null;
}
String code = "152362";
//此处应该生成验证码并调用发送短信相关接口,这里直接写死
//对验证码加密,加密规则与密码一致,方便shiro验证
String newCode = new SimpleHash("md5", code, ByteSource.Util.bytes(user.getUserName()), 2).toHex();
return newCode;
}
@Override
@Cacheable (value = "verificationCode")//校验验证码,直接从redis里获取,获取不到返回空
public String getVerificationCode(String phoneNum) {
// Cache cache = cacheManager.getCache("verificationCode");
// Object code = cache.get(phoneNum).get();
// String code = (String) cache.get(phoneNum).get();
return null;
}
}
public class MyShiroRealm extends AuthorizingRealm {
@Resource
@Lazy
private UserService userService;
@Resource
@Lazy
private VerificationCodeService verificationCodeService;
@Autowired(required = true)
private RedisSessionDAO redisSessionDAO;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
User user = (User)principals.getPrimaryPrincipal();
for(Role role:user.getRoleSet()){
authorizationInfo.addRole(role.getRoleName());
for(Permission p:role.getPermissionSet()){
authorizationInfo.addStringPermission(p.getPermissionName());
}
}
return authorizationInfo;
}
/*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
// //获取用户的输入的账号.
String principal = (String) token.getPrincipal();
User user = null;
SimpleAuthenticationInfo authenticationInfo = null;
//传进来的token是手机号,就用验证码替代密码进行校验
if(PhoneNumCheckUtil.checkPhoneNum(principal)){//用正则表达式校验是否为手机号码
user = userService.getUserByPhoneNum(principal);
if(user == null){
return null;
}
//从缓存里取出验证码
String code = verificationCodeService.getVerificationCode(principal);
if(null == code){
return null;
}
authenticationInfo = new SimpleAuthenticationInfo(
user,
code, //验证码
ByteSource.Util.bytes(user.getSalt()),
getName()
);
} else {
user = userService.getUserByName(principal);
if(user == null){
return null;
}
authenticationInfo = new SimpleAuthenticationInfo(
user, //user对象,存在redis session里,可自己定义存储内容,如用户名等
user.getPassword(), //数据库密码
ByteSource.Util.bytes(user.getSalt()),//salt
getName() //realm name
);
}
return authenticationInfo;
}
tip1:doGetAuthenticationInfo方法并不进行校验,只是获取相关数据并返回,校验还是按照其内部的校验规则,因此在存入缓存的时候也要对验证码进行和密码一样的加密
tip2:@Cacheable,@CachePut缓存失效的问题,网上的说法是因为shiro的bean先注入,spring的bean后注入,导致service的缓存和事务注释等都失效了,解决办法一个是懒加载
@Resource
@Lazy
private VerificationCodeService verificationCodeService;
一个是先不注入在使用的时候从spring容器里拿
VerificationCodeService verificationCodeService= ApplicationContextRegister.getBean(VerificationCodeService .class);