标签: token、java实现token认证、后台验证token
token验证是前后端交互中用的比较频繁的功能,这里我们的token采用前端cookie和后端session的方式来实现
首先token认证是用户通过浏览器登录成功后,后台返给前端的一个唯一标识,前端可以通过这个标识来访问后台接口
下面代码是我们从用户的登录到登录后的token认证的实现
没有异常处理的朋友可以参考一下【java自定义异常处理和抛出异常】传送门:https://blog.csdn.net/qq_39997045/article/details/112068918
1. 用户实体类
@Data
public class UserInfoQuery {
/**
* 用户主键
*/
private String id;
/**
* 用户姓名
*/
private String userName;
/**
* 昵称(用户名)
*/
private String nickname;
/**
* 密码
*/
private String password;
/**
* 邮箱
*/
private String mail;
/**
* 手机号
*/
private String phone;
/**
* 所属部门
*/
private String deptId;
/**
* 部门名称
*/
private String deptName;
/**
* 登陆次数
*/
private Integer logincount;
/**
* 权限
*/
private String roles;
/**
* 用户状态
*/
private Integer status;
/**
* accessToken
*/
private String accessToken;
}
2. 前端调用登录验证码来建立与后台的连接
这里我们将随机验证码返回给前端并将验证码保存到session中
"token:login:varicode:" + loginName; 将当前登录的用户和登录验证码存入redis
/**
* 获取登录图形验证码
*/
@ApiOperation("获取登录图形验证码")
@GetMapping("/getLoginCode")
public Map<String, Object> getLoginCode(
@ApiParam(value = "账号",required = false) @RequestParam(value = "loginName",required = false)String loginName,
HttpServletRequest request) throws ReturnException {
Map<String, Object> map = new HashMap<String, Object>();
// 验证码
String randomStr = StringUtil.getRandomStr(true, 4);
String key = "token:login:varicode:" + loginName;
try {
redisTemplate.opsForValue().set(key, randomStr);
} catch (Exception e) {
throw new ReturnException("0", "发送失败");
}
map.put("ret", "1");
map.put("msg", "成功");
map.put("data", randomStr);
return map;
}
3. 用户登录
controller层进行业务分发
先验证用户用户名密码是否正确
再将token等数据通过HttpServletResponse传给前端
登录后记得将验证码删掉
/**
* 用户登录
*/
@ApiOperation("用户登录")
@PostMapping("/webLogin")
public Map<String, Object> webLogin(
@ApiParam(value = "用户名",required = false) @RequestParam(value = "loginName",required = false)String loginName,
@ApiParam(value = "密码",required = false) @RequestParam(value = "password",required = false)String password,
@ApiParam(value = "图形验证码",required = false) @RequestParam(value = "varicode",required = false)String varicode,
HttpServletRequest request, HttpServletResponse resp) throws ReturnException {
Map<String, Object> map = new HashMap<String, Object>();
UserInfoQuery userInfo = userInfoService.webLogin(loginName, password, varicode);
//设置tokenKey
String tokenKey = StringUtil.getSUUID();
if (userInfo!=null) {
userInfo.setAccessToken(tokenKey);
userInfoService.setSession(tokenKey, userInfo, resp);
}
// 登录后记得将验证码删掉
redisTemplate.delete("token:login:varicode:" + loginName);
map.put("data", userInfo);
map.put("msg", "成功");
map.put("ret", "1");
return map;
}
Impl实现类
这里需要判断登录token是否有效
@Override
public UserInfoQuery webLogin(String loginName, String password, String varicode) throws ReturnException {
String key = "token:login:varicode:" + loginName;
if (!redisTemplate.hasKey(key)) {
throw new ReturnException("3", "登录超时");
}
if (!varicode.equals(redisTemplate.opsForValue().get(key))) {
throw new ReturnException("3", "验证码错误");
}
UserInfoQuery userInfoQuery = userInfoMapper.findLoginIsRepeat(loginName);
if (userInfoQuery==null) {
throw new ReturnException("3", "用户不存在");
}
if (!password.equals(userInfoQuery.getPassword())) {
throw new ReturnException("3", "账号或密码错误");
}
return userInfoQuery;
}
dao实现
@Select("select id, user_name, nickname, password, mail, phone, dept_id, dept_name, logincount, roles, status from app_user_info " +
"where nickname = #{loginName} or phone = #{loginName} or mail = #{loginName}")
UserInfoQuery findLoginIsRepeat(@Param("loginName") String loginName);
登陆成功后,设置浏览器token信息
这里将用户的token和用户的对象存入session
将token存入header头和cookie中并通过HttpServletResponse传给前端
/**
* 设置浏览器token
*/
@Override
public boolean setSession(String tokenKey, UserInfoQuery userInfoQuery, HttpServletResponse resp){
// 将token存入缓存中
// token在项目中使用得场景很多, 有的有效期较短,比如验证码、短信验证,也有有效期长的,比如登录后
// 这里在token后加个visit就是声明我是一个访问的token,与其他token进行区分
redisTemplate.opsForValue().set("token:visit:"+ tokenKey,tokenKey);
// 将用户的信息存入redis中, 存用户数据(这里没用到,但实际开发会通过缓存获取对象数据)
redisTemplate.opsForValue().set("token:data:visit:" + tokenKey, userInfoQuery);
//设置cookie 这是一段伪代码,具体实现根据项目实际情况进行设置
Cookie cookie = new Cookie("access_token", tokenKey);
// 将cookie存入resp相应中
resp.addCookie(cookie);
// 将token翻入消息头
resp.addHeader("access_token",tokenKey);
userInfoQuery.setAccessToken(null);
return true;
}
下面附postman测试
发送验证码
登录
后端返给前端的token
到这里一个简单的登录就做好了, 下面我们来进行token的认证
这里我们只做一个简单的案例
添加 LoginInterceptor 类继承 HandlerInterceptor 实现拦截
这里只做token不为空和是否有效
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate redisTemplate;
// 拼接token的键
String tokenKey = "token:visit:";
// 在请求处理之前调用,只有返回true才会执行要执行的请求
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
httpServletResponse.setCharacterEncoding("UTF-8");
String token=httpServletRequest.getHeader("access_token");
if (null==token){
throw new ReturnException("3", "请求非法");
}
// 判断token是否存在
String key = tokenKey+token;
if(!redisTemplate.hasKey(key)) {
throw new ReturnException("3", "token失效请重新登录");
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
2 通过自定义拦截器去拦截请求
通过重写 addInterceptors 来实现请求拦截,这里拦截的是 LoginInterceptor
@Configuration
@EnableSwagger2
public class BeanConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Autowired
private PubElConfig elConfig;
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/itlir").setViewName("/index/index");
registry.addViewController("/").setViewName("/toLogin");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(1800)
.allowedOrigins("*");
}
@Bean
public ReturnException setReturnException() {
return new ReturnException();
}
// 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
@Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("进入拦截器");
String excludePath = elConfig.getExcludePath();
List<String> excList = Arrays.asList(excludePath.split(","));
//addPathPatterns是表明拦截哪些请求
//excludePathPatterns是对哪些请求不做拦截
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excList);
}
}
PubElConfig 配置对象
@Setter
@Getter
@Component
public class PubElConfig {
@Value("${kafka-log-topic}")
private String kafkaLogTopic;
@Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;
@Value("${excludePath}")
private String excludePath;
}
application.yml 添加配置(顶格)
excludePath: "/api/sms/*,/api/userinfo/webLogin,/api/userinfo/registerUserInfo"