1.简介
刚刚换了公司,所以最近有些忙碌,所以一直没有什么地方,最近朋友问我登录相关的,所以这里先写文章简单使用令牌鉴权的文章,随后会补充一些高阶的,所以如果感觉这篇文章简单,可以直接绕行,言归正传,现在一般系统都进行了前分离,为了保证一定的安全性,现在很流行使用token来进行会话的验证,一般流程如下:
- 用户登录请求登录接口时,验证用户名密码等,验证成功会返回给前端一个令牌,这个令牌就是之后鉴权的唯一凭证。
- 后台可能将令牌存储在redis或数据库中。
- 之后前端的请求,需要在header中携带令牌,然后收回令牌去redis或者数据库中进行验证,如果验证通过则放行,如果不通过则拒绝操作。
当然,如上的说法只是简单的实现,有时还有很多需要优化的地方。
2.具体实现
2.1工程结构
本文工程结构如下:
其中:
- config:用于配置拦截器
- controller:这里只编写了LoginController(用于登录和重置)和TestController(用于测试未登录效果)
- 拦截器:编写拦截器代码
- 服务:只写了操作redis的代码和登录相关的代码
2.2代码实现
本文使用redis存储令牌信息,用户只是创建了一个固定的用户,在pom中加入相关依赖,完整内容如下:
配置文件中配置对应的redis信息,如下:<?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相关操作,此处示例只需要使用到获取,设置和删除操作,都是简单的使用RedisTemplate,RedisService内容如下:server.port=8888##redis配置spring.redis.host=localhostspring.redis.port=6379复制代码
LoginService只是进行登录和初始化操作,其中登录就是先判断用户名密码是否正确,如果正确,那么会生成一个字符串做为令牌(此处中使用uuid),并且做为返回值,密码错误则提示错误。恢复实质就是删除redis中令牌的缓存,完整内容如下: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;@Servicepublic class RedisService {@Resourceprivate 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);}}复制代码
LoginController内容很简单,只是对LoginService的简单调用,如下: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;@Servicepublic class LoginService {@Autowiredprivate 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 "注销成功!";}}复制代码
TestController中只是写了一个简单的返回字符串的接口,如下: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 {@Autowiredprivate 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);}}复制代码
接下来是拦截器,拦截器中需要收回header中的令牌,然后去redis中进行判断,如果存在,则允许操作,则返回提示信息,内容如下: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";}}复制代码
最后配置一下拦截器,由于拦截器中使用了RedisService,所以这里需要使用如下方式注入拦截器,内容如下: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 {@Autowiredprivate RedisService redisService;@Overridepublic 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;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}}复制代码
内容到这里就已经完成了。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;@Configurationpublic class AuthConfig implements WebMvcConfigurer {@Beanpublic AuthInterceptor initAuthInterceptor(){return new AuthInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(initAuthInterceptor()).addPathPatterns("/test/**").excludePathPatterns("/login/**");}}复制代码
3.测试
可以使用如下步骤进行简单测试:
首先在浏览器访问:http:// localhost:8888 / test,可以看到提示
用户未登录,请登录后操作!
在使用错误密码浏览器访问:http:// localhost:8888 / login?username = dalaoyang&password = 1,看到提示
用户名或密码错误,登录失败!
在浏览器使用用户名密码访问:http:// localhost:8888 / login?username = dalaoyang&password = 123,看到提示
用户:dalaoyang登录成功,token是:02fdd2bd-1669-48b9-b51b-1e724f97688f
使用http工具,如postman等,将令牌加入header中,请求:http:// localhost:8888 / test,看到提示,请求成功。
做点什么
4.扩展点
本文只是简单对token使用做成一个样例,并不适用于生产环境,有很多地方是可以扩展的,例如:
- 本文redis值存储的只是用户名,而且并没有利用,实际情况可以存储用户信息等。
- 可以扩展使用jwt进行令牌管理。
- 可以放行几个固定的令牌使用内部接口调用等。
- 注意token的生成规则,不要有规律让别人利用。
