SpringBoot实现token认证(基于缓存)

一、序言

本博客基于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");
    }

}

登录之后的结果:

 使用其他接口进行测试:

发布了125 篇原创文章 · 获赞 68 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_42109746/article/details/95634851