本文使用SpringBoot结合Redis进行简单的token鉴权。

1.简介

刚刚换了公司,所以最近有些忙碌,所以一直没有什么产出,最近朋友问我登录相关的,所以这里先写一篇简单使用token鉴权的文章,后续会补充一些高阶的,所以如果感觉这篇文章简单,可以直接绕行,言归正传,现在一般系统都进行了前后端分离,为了保证一定的安全性,现在很流行使用token来进行会话的验证,一般流程如下:

  1. 用户登录请求登录接口时,验证用户名密码等,验证成功会返回给前端一个token,这个token就是之后鉴权的唯一凭证。
  2. 后台可能将token存储在redis或者数据库中。
  3. 之后前端的请求,需要在header中携带token,后端取出token去redis或者数据库中进行验证,如果验证通过则放行,如果不通过则拒绝操作。

当然,如上的说法只是简单的实现,实质上还有很多需要优化的地方。

2.具体实现

2.1 工程结构

本文工程结构如下:

其中:

  • config:用于配置拦截器
  • controller:这里只编写了LoginController(用于登录和注销)和TestController(用于测试未登录效果)
  • interceptor:编写拦截器代码
  • service:只写了操作redis的代码和登录相关的代码

2.2 代码实现

本文使用redis存储token信息,用户只是创建了一个固定的用户,在pom中加入相关依赖,完整内容如下:

<?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 https://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>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dalaoyang</groupId>
<artifactId>springboot2_redis_login</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot2_redis_login</name>
<description>springboot2_redis_login</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

配置文件中配置对应的redis信息,如下:

server.port=8888
##redis配置
spring.redis.host=localhost
spring.redis.port=6379

接下来编写redis相关操作,本文示例只需要使用到get,set和delete操作,都是简单的使用RedisTemplate,RedisService内容如下:

package com.dalaoyang.service;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service
public class RedisService {
@Resource
private RedisTemplate<String,Object> redisTemplate; public void set(String key, Object value) {
//更改在redis里面查看key编码问题
RedisSerializer redisSerializer =new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
ValueOperations<String,Object> vo = redisTemplate.opsForValue();
vo.set(key, value);
} public Object get(String key) {
ValueOperations<String,Object> vo = redisTemplate.opsForValue();
return vo.get(key);
} public Boolean delete(String key) {
return redisTemplate.delete(key);
}
}

LoginService只是进行登录和注销操作,其中登录就是先判断用户名密码是否正确,如果正确,那么会生成一个字符串做为token(本文中使用uuid),并且做为返回值,密码错误则提示错误。注销实质就是删除redis中token的缓存,完整内容如下:

package com.dalaoyang.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.UUID; @Service
public class LoginService { @Autowired
private RedisService redisService; public String login(String username, String password) {
if (Objects.equals("dalaoyang", username) &&
Objects.equals("123", password)) {
String token = UUID.randomUUID().toString();
redisService.set(token, username);
return "用户:" + username + "登录成功,token是:" + token;
} else {
return "用户名或密码错误,登录失败!";
}
} public String logout(HttpServletRequest request) {
String token = request.getHeader("token");
Boolean delete = redisService.delete(token);
if (!delete) {
return "注销失败,请检查是否登录!";
}
return "注销成功!";
}
}

LoginController内容很简单,只是对LoginService的简单调用,如下:

package com.dalaoyang.controller;

import com.dalaoyang.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController
@RequestMapping("/login")
public class LoginController { @Autowired
private LoginService loginService; @GetMapping({"/", ""})
public String login(String username, String password) {
return loginService.login(username, password);
} @GetMapping("/logout")
public String logout(HttpServletRequest request) {
return loginService.logout(request);
}
}

TestController中只是写了一个简单返回字符串的接口,如下:

package com.dalaoyang.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/test")
public class TestController { @GetMapping({"/", ""})
public String dosomething() {
return "dosomething";
}
}

接下来是拦截器,拦截器中需要取出header中的token,然后去redis中进行判断,如果存在,则允许操作,则返回提示信息,内容如下:

package com.dalaoyang.interceptor;

import com.dalaoyang.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects; public class AuthInterceptor implements HandlerInterceptor { @Autowired
private RedisService redisService; @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)) {
response.getWriter().print("用户未登录,请登录后操作!");
return false;
}
Object loginStatus = redisService.get(token);
if( Objects.isNull(loginStatus)){
response.getWriter().print("token错误,请查看!");
return false;
}
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }
}

最后配置一下拦截器,由于拦截器中使用了RedisService,所以这里需要使用如下方式注入拦截器,内容如下:

package com.dalaoyang.config;

import com.dalaoyang.interceptor.AuthInterceptor;
import org.springframework.context.annotation.Bean;
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 AuthConfig implements WebMvcConfigurer { @Bean
public AuthInterceptor initAuthInterceptor(){
return new AuthInterceptor();
} @Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(initAuthInterceptor()).addPathPatterns("/test/**").excludePathPatterns("/login/**");
} }

内容到这里就已经完成了。

3.测试

可以使用如下步骤进行简单测试:

  1. 首先在浏览器访问:http://localhost:8888/test,可以看到提示

用户未登录,请登录后操作!

  1. 在使用错误密码浏览器访问:http://localhost:8888/login?username=dalaoyang&password=1,看到提示

用户名或密码错误,登录失败!

  1. 在浏览器使用用户名密码访问:http://localhost:8888/login?username=dalaoyang&password=123,看到提示

用户:dalaoyang登录成功,token是:02fdd2bd-1669-48b9-b51b-1e724f97688f

  1. 使用http工具,如postman等,将token放入header中,请求:http://localhost:8888/test,看到提示,请求成功。

dosomething

4.扩展点

本文只是简单对token使用做了一个样例,并不适用于生产环境,有很多地方是可以扩展的,比如:

  1. 本文redis值存储的只是username,而且并没有利用,实际情况可以存储用户信息等。
  2. 可以扩展使用jwt进行token管理。
  3. 可以放行几个固定的token用作内部接口调用等。
  4. 注意token的生成规则,不要有规律让别人利用。

5.源码

源码地址:https://gitee.com/dalaoyang/springboot_learn/tree/master/springboot2_redis_login

最新文章

  1. angularjs指令系统系列课程(4):作用域Scope
  2. idea debug 变慢
  3. Windows配置端口转发
  4. Android第三方授权(新浪微博篇)
  5. jar中没有主清单属性
  6. XStream的使用方法、简单使用方法、xml的解析方法
  7. js 常用正则表达式表单验证代码
  8. TabLayout学习笔记
  9. 宿主机无法访问linux虚机中的网站
  10. java 远程调试方法
  11. datatable中的copy和clone的用法区分
  12. iframe和form表单实现ajax请求上传数据
  13. Android为TV端助力 遥控器的映射
  14. hdu3790 dijkstra+堆优化
  15. json和数组的区别
  16. seller【3】目录接口&amp;header组件 -【配置相对地址】
  17. spring中bean配置和注入场景分析
  18. VirtualBox 安装 Gentoo 小记
  19. 〖Linux〗上传单个文件到FTP的Shell命令行(函数)
  20. E - 追求

热门文章

  1. Python字典(Dictionary)update()方法
  2. spring的@primary和@qualifier注解解决一个接口多个实现的注入问题
  3. 截图自动添加水印图片工具 pickpick设置中文语言
  4. Redis set集合
  5. 【题解】NOIP2016提高组 复赛
  6. Asp.net HttpContext 简介
  7. WebApi使用Unity实现IOC
  8. MVC 创建Controllers 发生 EntityType has no key defined error
  9. linux系统下安装配置Jenkins
  10. Windows+Qt+MinGW使用gRPC