对于很多系统来说,登陆权限控制是每个系统都具有的,不过实现的方案也多种多样。
下面利用简单的demo来实现使用 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 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>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.scorpios</groupId>
<artifactId>token-authentication</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>token-authentication</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.0</version>
</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>1.3.2</version>
</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>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
sql脚本(很简单 ,但还是贴出来吧):
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`username` varchar(20) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'csq', '123456');
配置文件:
#数据库连接设置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springcloud-token
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.configuration.map-underscore-to-camel-case=true
־
logging.level.com.scorpios.cache.mapper=debug
#redis的设置,如果你的redis没有设置密码,那么spring.redis.password就不用写了,
#注释掉就可以,我的redis在本地,没有设置初始密码,所以直接注释掉
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=123456
自定义注解:AuthToken
package com.csq.study.springcloud.token.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @Description: TODO
* @ClassName: AuthToken
* @author chengshengqing 2019年7月2日
* @see TODO
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthToken {
}
配置类:WebAppConfiguration
package com.csq.study.springcloud.token.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.csq.study.springcloud.token.interceptor.TokemInterceptor;
/**
*
* @Description: TODO
* @ClassName: WebAppConfiguration
* @author chengshengqing 2019年7月2日
* @see TODO
*/
@Configuration
public class WebAppConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokemInterceptor()).addPathPatterns("/**");
}
}
controller:TokenController
package com.csq.study.springcloud.token.controller;
import com.alibaba.fastjson.JSONObject;
import com.csq.study.springcloud.token.UserDao.UserDao;
import com.csq.study.springcloud.token.annotation.AuthToken;
import com.csq.study.springcloud.token.entity.ResponseTemplate;
import com.csq.study.springcloud.token.entity.User;
import com.csq.study.springcloud.token.util.CommonUtil;
import com.csq.study.springcloud.token.util.Md5TokenGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
/**
*
* @Description: TODO
* @ClassName: welcome
* @author chengshengqing 2019年7月2日
* @see TODO
*/
@RestController
public class TokenController {
Logger logger = LoggerFactory.getLogger(TokenController.class);
@Autowired
private Md5TokenGenerator tokenGenerator;
@Autowired
private UserDao userDao;
@GetMapping("/welcome")
public String Welcome(){
return "welcome TokenController ";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public ResponseTemplate Login(String username, String password) {
logger.info("用户名username为:"+username+ "密码password为:"+password);
User user = userDao.getUser(username,password);
logger.info("从数据库查出来的用户user为:"+user);
JSONObject obj = new JSONObject();
if (user != null) {
String token = SetRedisData(username, password);
obj.put("status", "用户登录成功");
obj.put("token", token);
} else {
obj.put("status", "用户登录失败");
}
return ResponseTemplate.builder().code(200).message("用户登录成功").data(obj).build();
}
@RequestMapping(value = "test", method = RequestMethod.GET)
@AuthToken
public ResponseTemplate test() {
logger.info("**************测试start**************");
return ResponseTemplate.builder().code(200).message("测试成功").data("测试数据").build();
}
/**
* 在redis中进行数据的绑定
* @Title: SetRedisData
* @Description: TODO
* @param username
* @param password
* @return
* @author chengshengqing 2019年7月2日
*/
private String SetRedisData(String username, String password) {
//此处主要设置你的redis的ip和端口号,我的redis是在本地
Jedis jedis = new Jedis("127.0.0.1", 6379);
String token = tokenGenerator.generate(username, password);
jedis.set(username, token);
//设置key过期时间,到期会自动删除
jedis.expire(username, CommonUtil.TOKEN_EXPIRE_TIME);
//将token和username以键值对的形式存入到redis中进行双向绑定
jedis.set(token, username);
jedis.expire(token, CommonUtil.TOKEN_EXPIRE_TIME);
Long currentTime = System.currentTimeMillis();
jedis.set(token + username, currentTime.toString());
//用完关闭
jedis.close();
return token;
}
}
package com.csq.study.springcloud.token.entity;
import java.io.Serializable;
/**
*
* @Description: TODO
* @ClassName: User
* @author chengshengqing 2019年7月2日
* @see TODO
*/
public class User implements Serializable{
/**
* @Fields serialVersionUID : TODO
*/
private static final long serialVersionUID = 1L;
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + "]";
}
}
ResponseTemplate 类
package com.csq.study.springcloud.token.entity;
import lombok.Builder;
@Builder
public class ResponseTemplate {
public Integer code;
public String message;
public Object data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
拦截器(重点):
package com.csq.study.springcloud.token.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.csq.study.springcloud.token.annotation.AuthToken;
import com.csq.study.springcloud.token.util.CommonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import redis.clients.jedis.Jedis;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
/**
*
* @Description: TODO
* @ClassName: AuthorizationInterceptor
* @author chengshengqing 2019年7月2日
* @see TODO
*/
public class TokemInterceptor implements HandlerInterceptor {
Logger log = LoggerFactory.getLogger(TokemInterceptor.class);
//存放鉴权信息的Header名称,默认是Authorization
private String Authorization = "Authorization";
//鉴权失败后返回的HTTP错误码,默认为401
private int unauthorizedErrorCode = HttpServletResponse.SC_UNAUTHORIZED;
/**
* 存放用户名称和对应的key
*/
public static final String USER_KEY = "USER_KEY";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//验证token
if (method.getAnnotation(AuthToken.class) != null || handlerMethod.getBeanType().getAnnotation(AuthToken.class) != null) {
String token = request.getParameter(Authorization);
log.info("获取到的token为: {} ", token);
//此处主要设置你的redis的ip和端口号,我的redis是在本地
Jedis jedis = new Jedis("127.0.0.1", 6379);
String username = null;
if (token != null && token.length() != 0) {
//从redis中根据键token来获取绑定的username
username = jedis.get(token);
log.info("从redis中获取的用户名称为: {}", username);
}
//判断username不为空的时候
if (username != null && !username.trim().equals("")) {
String startBirthTime = jedis.get(token + username);
log.info("生成token的时间为: {}", startBirthTime);
Long time = System.currentTimeMillis() - Long.valueOf(startBirthTime);
log.info("token存在时间为 : {} ms", time);
//重新设置Redis中的token过期时间
if (time > CommonUtil.TOKEN_RESET_TIME) {
jedis.expire(username, CommonUtil.TOKEN_EXPIRE_TIME);
jedis.expire(token, CommonUtil.TOKEN_EXPIRE_TIME);
log.info("重置成功!");
Long newBirthTime = System.currentTimeMillis();
jedis.set(token + username, newBirthTime.toString());
}
//关闭资源
jedis.close();
request.setAttribute(USER_KEY, username);
return true;
} else {
JSONObject jsonObject = new JSONObject();
PrintWriter out = null;
try {
response.setStatus(unauthorizedErrorCode);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
jsonObject.put("code", ((HttpServletResponse) response).getStatus());
//鉴权失败后返回的错误信息,默认为401 unauthorized
jsonObject.put("message", HttpStatus.UNAUTHORIZED);
out = response.getWriter();
out.println(jsonObject);
return false;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != out) {
out.flush();
out.close();
}
}
}
}
request.setAttribute(USER_KEY, null);
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 {
}
}
userDao:
package com.csq.study.springcloud.token.UserDao;
import org.apache.ibatis.annotations.*;
import com.csq.study.springcloud.token.entity.User;
@Mapper
public interface UserDao {
@Select("select * from user where username=#{username} and password=#{password}")
public User getUser(@Param("username") String username,@Param("password") String password);
}
工具类:CommonUtil
package com.csq.study.springcloud.token.util;
public final class CommonUtil {
/**
* redis存储token设置的过期时间,10分钟
*/
public static final Integer TOKEN_EXPIRE_TIME = 60 * 10;
/**
* 设置可以重置token过期时间的时间界限
*/
public static final Integer TOKEN_RESET_TIME = 1000 * 100;
}
Md5TokenGenerator 类
package com.csq.study.springcloud.token.util;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
@Component
public class Md5TokenGenerator implements TokenGenerator {
@Override
public String generate(String... strings) {
long timestamp = System.currentTimeMillis();
String tokenMeta = "";
for (String s : strings) {
tokenMeta = tokenMeta + s;
}
tokenMeta = tokenMeta + timestamp;
String token = DigestUtils.md5DigestAsHex(tokenMeta.getBytes());
return token;
}
}
TokenGenerator 接口:
package com.csq.study.springcloud.token.util;
import org.springframework.stereotype.Component;
@Component
public interface TokenGenerator {
public String generate(String[] strings);
}
启动类:TokenApplication
package com.csq.study.springcloud.token;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TokenApplication {
public static void main(String[] args) {
SpringApplication.run(TokenApplication.class, args);
}
}
启动项目,然后启动redis
再访问:http://localhost:8080/login?username=csq&password=123456
看到如下截图:
然后 我们访问:http://localhost:8080/test?Authorization=43ebef36c1d83f54d2606f10197dcef2
然后我们在稍微改一下那个token:访问: http://localhost:8080/test?Authorization=43ebef36c1d83f54d2606f10197dcef4
看截图:
然后我们去看下控制台输出的截图:
以上说明 :我们的token实现权限控制小demo搭建成功了。
此demo容易出错的地方:
1.redis错误,我们在本地安装的redis一般默认是没有密码的。所以在配置文件中不需要配置密码
2.redis需要在访问项目之前启动
3.在eclipse中lombok的jar包问题有的会导致@Builder注解不能用,可以试试这个方法(请点击查看),就可以解决这个问题