流程:
以下内容可拷贝直接用。
1.1登录成功。把token和loginInfo 放进 通过localStorage放进本地浏览器。类似于cookie。
2.1 axois前置拦截。作用就是把登录之后的每个请求头里面加上token 表示是登录过的标识
2.2 后端java代码做拦截配置 。从redis里面取出key--token,如果有值就再次设置30分钟有效期。为null的话,使用输出流返回错误信息。
2.3 axois的后置拦截,实际就是对后端拦截出来的报错信息进行处理
2.4 前端静态资源拦截也可以叫做路由拦截。 如果通过路由访问登录和注册就放行。如果是访问特定资源。就从之前localStorage取信息。也可也从token取。如果有值。那么就放行。否则跳转登录页面。
1接口先行
1.1controller层
package cn.wl.user.controller;
import cn.wl.basic.exception.BusinessException;
import cn.wl.basic.util.AjaxResult;
import cn.wl.user.dto.LoginDto;
import cn.wl.user.service.ILoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/login")
public class LoginController {
@Autowired
private ILoginService loginService;
/**
* 账号密码登录 前后端公用
*/
@PostMapping("/account")
public AjaxResult accountLogin(@RequestBody LoginDto loginDto){
try {
Map<String, Object> map =loginService.accountLogin(loginDto);
return AjaxResult.me().setResultObj(map);//返回 token串和 loginInfo基本信息
} catch (BusinessException e) {
e.printStackTrace();
return AjaxResult.fail(e.getMessage());//接受service层抛出的异常
}catch (Exception e) {
e.printStackTrace();
return AjaxResult.fail("铁汁系统错误,请稍后重试");
}
}
}
1.2service层
package cn.wl.user.service;
import cn.wl.user.dto.LoginDto;
import java.util.Map;
/**登录。主站和前台公用*/
public interface ILoginService {
Map<String,Object> accountLogin(LoginDto loginDto);
}
1.3处理实际业务的impl层
由于是前后端共有登录接口。表涉及用户表,平台员工表,中间表。中间表抽取两种表的登录信息。t通过前端传递的type类型判断是用户还是平台员工登录。0.平台的人。1用户。
package cn.wl.user.service.impl;
import cn.wl.basic.exception.BusinessException;
import cn.wl.basic.util.MD5Utils;
import cn.wl.basic.util.StrUtils;
import cn.wl.user.domain.LoginInfo;
import cn.wl.user.dto.LoginDto;
import cn.wl.user.mapper.LoginInfoMapper;
import cn.wl.user.service.ILoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**共有登录接口*/
@Service
public class LoginServiceImpl implements ILoginService {
@Autowired
private LoginInfoMapper loginInfoMapper;
@Autowired
private RedisTemplate redisTemplate;
//1效验参数是否为空
//2查询数据库比对用户名 注意要比对type
//2.1查询不到。抛出异常
//3用户是否被禁用
//4如果查询到了,比对密码 //使用相同盐值,加密登录密码,用来作比对 如果密码不一致,抛异常
//5 密码一致:存储redis
//6 返回token和logininfo基本信息给前端 我们不应该把敏感信息返回给前端 密码 salt
@Override
public Map<String, Object> accountLogin(LoginDto loginDto) {
if(StringUtils.isEmpty(loginDto.getUsername())||StringUtils.isEmpty(loginDto.getPassword())){
throw new BusinessException("请填写完整信息,注意所填内容不能为空");
}
LoginInfo loginInfo = loginInfoMapper.loadByUsername(loginDto.getUsername(),loginDto.getType());
if(loginInfo==null){
throw new BusinessException("帐号不存在");
}
if(loginInfo.getDisable()==0){
throw new BusinessException("帐号因违规,已经被封禁,如需申诉,请联系客服");
}
String salt = loginInfo.getSalt();
String s = MD5Utils.encrypByMd5(loginDto.getPassword() + salt);
if(!(s).equals(loginInfo.getPassword())){
throw new BusinessException("密码错误");
}else {
String token = UUID.randomUUID().toString();
//把登录成功的用户信息,放进redis服务器。
redisTemplate.opsForValue().set(token,loginInfo,30, TimeUnit.MINUTES); //30分钟后过期
Map<String, Object> map = new HashMap<String, Object>();
map.put("token",token);
loginInfo.setSalt(null);
loginInfo.setPassword(null);
map.put("loginInfo",loginInfo);
return map;
}
}
}
1.4mapper层
package cn.wl.user.mapper;
import cn.wl.user.domain.LoginInfo;
public interface LoginInfoMapper{
LoginInfo loadByUsername(String username,Integer type);
/*下面写拓展的方法*/
}
1.5InfoMapper.xml
由于mybaits多参数查询写法有多种。这里我选择了一种写法 0代表username 1代表type
usernam。phone。email为登录中间表的信息。如果匹配了一个自动就说明有这个用户
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.loginInfo//DTD Mapper 3.0//EN"
"http://mybatis.loginInfo/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.wl.user.mapper.LoginInfoMapper">
<!--LoginInfo loadByUsername(String username,Integer type);-->
<select id="loadByUsername" resultType="LoginInfo">
SELECT * FROM t_logininfo where (username=#{0} or phone=#{0} or email=#{0}) AND
type = #{1}
</select>
</mapper>
2.1 用户的domain
User
package cn.wl.user.domain;
import cn.wl.basic.domain.BaseDomain;
import lombok.Data;
import java.util.Date;
@Data
public class User extends BaseDomain {
//id来自继承 BaseDomain
private String username;
private String email;
private String phone;
/**盐值*/
private String salt;
private String password;
private Integer state =1; //直接激活
private String age;
private Date createtime = new Date();
private String headImg;
private Long logininfo_id; //关联的中间表信息
}
2.2平台的人
Employee
package cn.wl.org.domain;
import cn.wl.basic.domain.BaseDomain;
import lombok.Data;
@Data
public class Employee extends BaseDomain{
//id来自继承 BaseDomain
private String username;
private String email;
private String phone;
/**加密密码*/
private String salt; //后续使用MD5技术对密码进行加密
private String password;
private Integer age;
private Integer state = 1 ;
private Long department_id ;
private Long logininfo_id;
private Long shop_id;
}
3 User表设计
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(255) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL COMMENT '盐值',
`password` varchar(255) DEFAULT NULL COMMENT '密码,md5加密加盐',
`state` int(11) DEFAULT NULL COMMENT '已注册 已激活 禁用',
`age` int(2) DEFAULT NULL,
`createtime` datetime DEFAULT NULL,
`headImg` varchar(255) DEFAULT NULL,
`logininfo_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
3.1 Employee表
CREATE TABLE `t_employee` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(255) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`state` int(1) DEFAULT NULL,
`department_id` bigint(20) DEFAULT NULL,
`logininfo_id` bigint(20) DEFAULT NULL,
`shop_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK4AFD4ACE851EFECF` (`department_id`)
) ENGINE=InnoDB AUTO_INCREMENT=388 DEFAULT CHARSET=utf8;
中间表 t_logininfo
CREATE TABLE `t_logininfo` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`phone` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`type` int(22) DEFAULT NULL COMMENT '0 代表管理员 1 用户',
`disable` int(22) DEFAULT NULL COMMENT '0 不可以用 1 可用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
用到的工具类 1 AjaxResult 返回给前端, resultObj可携带对象集合给到前端展示
package cn.wl.basic.util;
import lombok.Data;
@Data
public class AjaxResult {
private Boolean success = true;
private String message = "操作成功";
private Object resultObj;
public static AjaxResult me(){
return new AjaxResult();
}
public AjaxResult setResultObj(Object resultObj) {
this.resultObj=resultObj;
return this;
}
//失败构造
public static AjaxResult fail(String message){
return me().setSuccess(false).setMessage(message);
}
//链式编程
public AjaxResult setSuccess(Boolean success) {
this.success = success;
return this;
}
//链式编程
public AjaxResult setMessage(String message) {
this.success = false;
this.message = message;
return this;
}
}
工具类2 MD5Utils 作用对密码加密
package cn.wl.basic.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Utils {
/**
* 加密
* @param context
*/
public static String encrypByMd5(String context) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(context.getBytes());//update处理
byte [] encryContext = md.digest();//调用该方法完成计算
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < encryContext.length; offset++) {//做相应的转化(十六进制)
i = encryContext[offset];
if (i < 0) i += 256;
if (i < 16) buf.append("0");
buf.append(Integer.toHexString(i));
}
return buf.toString();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
//加密
//1 生成随机盐值
String pwd = "1";
String salt = StrUtils.getComplexRandomString(32);
//2 通过这个盐值加密
String md5Pwd = MD5Utils.encrypByMd5(pwd +"yhp"+ salt+"xxxx");
System.out.println(md5Pwd);
//密码比对
//1 查询盐值-就是salt
String saltTmp = salt;
//3 加密比对
String pwdTmp = "1";
String inputMd5Pwd = MD5Utils.encrypByMd5(pwdTmp +"yhp"+ saltTmp+"xxxx");
if (inputMd5Pwd.equals(md5Pwd)){
System.out.println("登录成功!");
}else{
System.out.println("密码错误");
}
}
}
一:前端对接口传递的数据进行处理
this.$http.post("/login/account",this.ruleForm2)//this.ruleForm2为表单内容
.then(result =>{
result=result.data;
if(result.success){
this.logining=false;
let{token,loginInfo}= result.resultObj;
localStorage.setItem("token",token);//后端传递的map中的数据
localStorage.setItem("loginInfo",JSON.stringify(loginInfo))
this.$router.push({ path: '/echarts' }); //跳转主页
}else {
this.logining = false;
this.$message({
message:result.message,
type:'error'
})
}
})
以下开始做拦截配置 。一共4个拦截器。目前是企业中公认配置最优化的拦截
1axios前置拦截
//--------第一个axios前置拦截器
axios.interceptors.request.use(config=>{
let token = localStorage.getItem("token");
if(token){
config.headers['token']=token;//请求头携带token到后端
}
return config;
},error => {Promise.reject(error)});
2 后台java代码配置
package cn.wl.basic.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration//声明是这是一个配置类
public class WebConfigurer implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//放行swagger访问的路径配置
String[] swaggerExcludes = new String[]{"/swagger-ui.html","/swagger-resources/**","/webjars/**"};
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//拦截所有的请求
//不需要拦截的请求
.excludePathPatterns("/user/register/**")
.excludePathPatterns("/verifycode/**")
.excludePathPatterns("/login/**")
.excludePathPatterns("/fastDfs/**").excludePathPatterns(swaggerExcludes);
}
}
1.1使用拦截配置类
package cn.wl.basic.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
/** 登录拦截与放行 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String token = request.getHeader("token");
if (!StringUtils.isEmpty(token)){//不是空的,说明已经登录过了
Object o = redisTemplate.opsForValue().get(token);
if( o != null){
redisTemplate.opsForValue().set(token,o,30, TimeUnit.MINUTES);//如果30分钟不操作。自动过期,需要重新登录
return true;
}
}
//如果token为空的话,直接返回以下信息给到 第三个:axios的后置拦截器
response.setContentType("application/json;charset=utf-8");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.write("{\"success\":false,\"message\":\"noLogin\"}");
writer.flush();
writer.close();
return false;//阻值放行
}
}
3 axios的后置拦截器 处理后端第二个拦截器的报错信息
//第三个:axios的后置拦截器 处理后端第二个拦截器的报错信息
axios.interceptors.response.use(config=>{
let data = config.data;
if(!data.success && "noLogin"===data.message)
{
localStorage.removeItem("token");
localStorage.removeItem("loginInfo");
router.push({ path: '/login' });
}
return config;
},error => {
Promise.reject(error)
})
4 前端静态资源拦截器也叫做路由拦截
//登录和注册在没有登录情况下也能访问
router.beforeEach((to, from, next) => {
if (to.path == '/login'|| to.path=="/shopin") {
next()
}else{
//否则需要判断是否已经登录
let user = localStorage.getItem('loginInfo');
if (!user) { //未登录
next({ path: '/login' })
} else {
next() //登录了,正常执行
}
}
})
一共4个拦截器。从axios前置拦截开始---后端 ---- axois后置拦截---静态资源也叫路由拦截
xios前置拦截:拦截请求发出来的请求。其实就是把请求头里面写上token信息,给后端拦截判断是否登录过
后端拦截:不拦截那些请求。放行那些请求。 1取出token 如果取不到,写data数据给到axios后置垃圾
axois后置拦截:处理后端传递来的信息
静态资源也叫路由拦截:如果直接访问公共的路由 放行资源