一、序言
本博客基于SpringBoot,使用redis缓存实现token认证,来验证用户身份的合法性。
二、什么是token?
token意为令牌,为一个随机的字符串UUID生成,用来标记来访用户的身份,通过该token,可以得出是哪一个用户向我服务器访问资源。
三、验证流程
当用户登录成功之后,则向客户端发送token,用来标记该用户,以后每次用户向服务器访问时,则在请求头带上该token。服务器从请求头中获取token,拿到该token之后,向redis缓存查找是否存在此token,如存在,则返回给用户相应的资源,若不存在,则返回,不给与资源。
四、程序实现
1.引入Maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.21.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.chen</groupId>
<artifactId>cachetest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cachetest</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.1.6.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- 配置使用redis启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 集成lombok简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--将对象转为json字符串-->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.创建user表
3.编写application.properties
#配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
#开启驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
#配置redis
spring.redis.host=localhost
spring.redis.port=6379
#打印SQL语句日志
logging.level.com.chen.mapper=debug
4.编写用户类User
package com.chen.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
int id;
String username;
String password;
}
5.编写Mapper
package com.chen.mapper;
import com.chen.bean.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("SELECT * FROM `user` WHERE username=#{username} AND password=#{password}")
public User getUser(User user);
}
6.编写Service
package com.chen.service;
import com.chen.bean.User;
import com.chen.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public User getUser(User user) {
User u = userMapper.getUser(user);
return u;
}
}
7.编写Msg类,用来封装返回的信息
package com.chen.util;
import java.util.HashMap;
import java.util.Map;
public class Msg {
// 状态码: 200-成功 400-失败 500-异常
private int status;
// 提示信息
private String message;
// 用户返回给浏览器的数据
private Map<String, Object> data = new HashMap<String, Object>();
public static Msg success() {
Msg result = new Msg();
result.setStatus(200);
result.setMessage("处理成功!");
return result;
}
public static Msg fail() {
Msg result = new Msg();
result.setStatus(400);
result.setMessage("处理失败!");
return result;
}
public static Msg error() {
Msg result = new Msg();
result.setStatus(500);
result.setMessage("未知异常!");
return result;
}
public Msg add(String key, Object value) {
this.data.put(key, value);
return this;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Map<String, Object> getData() {
return data;
}
public void setData(Map<String, Object> data) {
this.data = data;
}
}
8.编写Controller
package com.chen.controller;
import com.chen.bean.User;
import com.chen.service.UserService;
import com.chen.util.Msg;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
public class UserController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private UserService userService;
@PostMapping("/login")
public Msg login(User user) {
User u = userService.getUser(user);
if (u != null) {
String token = UUID.randomUUID().toString().replaceAll("-", "");
stringRedisTemplate.opsForValue().set(token, String.valueOf(u.getId()), 3600, TimeUnit.SECONDS);//将用户的ID信息存入redis缓存,并设置一小时的过期时间
return Msg.success().add("token",token).add("info","登录成功");
}else {
return Msg.fail().add("info", "登录失败");
}
}
@PostMapping("/other")
public Msg other() {
return Msg.success().add("info", "该接口是来测试的");
}
}
9.配置启动类,使mapper生效
package com.chen;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@MapperScan("com.chen.mapper")
@SpringBootApplication
@EnableCaching
public class CachetestApplication {
public static void main(String[] args) {
SpringApplication.run(CachetestApplication.class, args);
}
}
10.设置拦截器,在进行接口访问前,先验证token的正确性
package com.chen.interceptor;
import com.chen.util.Msg;
import org.json.JSONObject;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Component
public class ParamInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
String token = httpServletRequest.getHeader("token");
//token验证
if(token!=null){
String id = stringRedisTemplate.opsForValue().get(token);
if(id!=null){
System.out.println("token验证成功");
return true;
}else{
System.out.println("token验证失败");
returnJson(httpServletResponse);
return false;
}
}else{
System.out.println("token验证失败");
returnJson(httpServletResponse);
return false;
}
}
private void returnJson(HttpServletResponse response){
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try {
writer = response.getWriter();
Msg msg = Msg.fail().add("info", "没有token或token无效");
JSONObject jsonObject = new JSONObject(msg);
writer.print(jsonObject);
} catch (IOException e){
e.printStackTrace();
} finally {
if(writer != null){
writer.close();
}
}
}
@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 {
}
}
11.配置拦截器,使前一步设置的拦截器生效
package com.chen.config;
import com.chen.interceptor.ParamInterceptor;
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.WebMvcConfigurationSupport;
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Autowired
private ParamInterceptor paramInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
//此处配置拦截路径
registry.addInterceptor(paramInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
}
登录之后的结果:
使用其他接口进行测试: