一.简述
token其实就是一个身份标识,或者说是一个身份牌,因为几乎所有的应用需要注册登陆,当我们登陆成功的时候,就要有一个登录成功的身份牌,这样那些没有身份牌的访问就会被服务器给拦截掉无法访问,用来过滤非法请求。
以前session的做法毕竟比较局限,而且当我们微服务,多实例的时候,session之间是不能够共享的,我们总不能使用rtc多个实例间调用session验证是否登陆吧? 所以我们使用验证token的方式去校验请求合法性。
二.创建项目
新建Maven项目,在父目录下新建model

目录结构
suns下pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
| <?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">
<parent>
<artifactId>suns</artifactId>
<groupId>org.zhc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sso</artifactId>
<dependencies>
<dependency>
<groupId>org.zhc</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.0</version>
</dependency>
</dependencies>
</project>
|

api目录
api下pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| <?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">
<parent>
<artifactId>suns</artifactId>
<groupId>org.zhc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
|

sso下pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
| <?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">
<parent>
<artifactId>suns</artifactId>
<groupId>org.zhc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sso</artifactId>
<dependencies>
<dependency>
<groupId>org.zhc</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.0</version>
</dependency>
</dependencies>
</project>
|
三.项目配置
sso 主要用来登录处理
api 是所有实例中共用的一些类库,可以自己封装一些工具等
suns 项目的根节点
api和suns中基本没有配置,他们都主要是为了maven版本统一,代码解耦
项目中使用nacos作为服务的注册中心,后面会记录有会记录搭建nacos
Nacos 快速开始nacos.io

suns下application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
| server:
port: 8089
spring:
application:
name: sso_01
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
redis:
#集群模式
# cluster:
# nodes:
# - 192.198.200.52:6379
# - 192.198.200.53:6379
# - 192.198.200.54:6379
#单机模式
database: 0
host: 127.0.0.1
port: 6379
password:
jedis:
pool:
#最大连接数
max-active: 100
#最大阻塞等待时间(负数表示没限制)
max-wait: 60s
#最大空闲
max-idle: 30
#最小空闲
min-idle: 0
timeout: 100s
ssl: false
|
suns下RedisConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
| package com.zhc.config;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//更改在redis里面查看key编码问题
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
// redisTemplate.setValueSerializer(new StringRedisSerializer());
// redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
|
四.登录接口
流程大概是,当客户端在调用接口的时候,携带用户名和密码,或者验证码,然后我们验证登录是否成功,成功的时候使用JWT生成token,将token保存到redis,并且设置过期时间,一般设置个两小时左右,设置的时间不能太久。
至于为什么使用JWT,传统的cookies+redis实际上更好用,不要问,问就是我闲的。其实JWT更适合用在校验上,大有一种申请,考核之类的意思,一般频繁的使用同一个JWT串也是不可靠的,大多在一次性的场景,或者每次都生成新的密文,可能更适合它。
这里我用JWT生成token,添加过期,考虑到安全性和传输数据量,在token中不适合添加太多的数据。
token肯定会有一个过期时间,并且过期时间越短,安全性越高,如果你过期设置几秒的话,那就每个请求到需要重新登录,这岂不是很不方便。
所以,我在JWT生成token的时候,同时生成一个refreshToken将它保存到redis,或者你也可以存到数据库,设置一个比较长的过期时间,一星期,一个月,在返回token的时候,将refreshToken也返回给用户,当用户的token过期之后,就使用refreshToken重新生成新的token,用户可以不需要在重新登录。
当refreshToken也过期的时候,这个时候,我们再去提醒用户,需要重新登录了。
JWT的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
| package com.zhc.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
public class JwtUtil {
public static final String SECRET_KEY = "123456"; //秘钥
public static final long TOKEN_EXPIRE_TIME = 5 * 60 * 1000; //token过期时间 秒
public static final long REFRESH_TOKEN_EXPIRE_TIME = 24 * 60 * 60 * 1000; //refreshToken过期时间 秒
private static final String ISSUER = "tian_wang_gai_du_hu"; //签发人
/**
* 生成签名
*/
public static String generateToken(String info){
Date now = new Date();
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY); //算法
String token = JWT.create()
.withIssuer(ISSUER) //签发人
.withIssuedAt(now) //签发时间
.withExpiresAt(new Date(now.getTime() + TOKEN_EXPIRE_TIME)) //过期时间
.withClaim("info", info) //保存身份标识
.sign(algorithm);
return token;
}
/**
* 验证token
*/
public static boolean verify(String token){
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY); //算法
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
verifier.verify(token);
return true;
} catch (Exception ex){
ex.printStackTrace();
}
return false;
}
/**
* 从token获取username
*/
public static String getUsername(String token){
try{
return JWT.decode(token).getClaim("info").asString();
}catch(Exception ex){
ex.printStackTrace();
}
return "";
}
}
|
redis工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
| package com.zhc.common;
import com.zhc.util.JwtUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class TokenBuffer {
@Resource(name = "redisTemplate")
private RedisTemplate redisTemplate;
public void put(String key,String value){
redisTemplate.opsForValue().set(key,value,60*120 , TimeUnit.SECONDS);
}
public String get(String key){
return (String)redisTemplate.opsForValue().get(key);
}
public void change(String key,String user){
redisTemplate.opsForValue().set(key,user,60*120 , TimeUnit.SECONDS);
}
public void updateTime(String key,Long timeout){
redisTemplate.expire(key,timeout,TimeUnit.SECONDS);
}
public void putHash(String key1,String key2,String value){
redisTemplate.opsForHash().put(key1, key2, value);
}
public String getHash(String key1,String key2){
return (String)redisTemplate.opsForHash().get(key1, key2);
}
}
|
登录接口(简化)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| @Slf4j
@RestController
public class LoginController {
@Autowired
private TokenBuffer tokenBuffer;
@GetMapping("login")
public Message login(){
User user = new User();
user.setUserId(123456L);
user.setUserName("123456");
user.setPassword("123456");
//String token = UUID.randomUUID().toString().replace("-","");
String token = JwtUtil.generateToken(user.getUserId());
tokenBuffer.put("com.zhc.suns:"+user.getUserId(),user.toString());
JSONObject jsonObject = new JSONObject();
jsonObject.put("token",token);
jsonObject.put("user",user);
log.info(jsonObject.toJSONString());
return Message.success(user);
}
}
|
上面就完成了一个简单的token验证,说到安全的话还是用https加证书验证去防止抓包。
现在我们把token发给前端,前端再请求的时候加上,我们再写个过滤器去过滤他,当然我这里是微服务的形式,我放到了gateway中去验证的,下个笔记再记,这个用在单体的小项目中也是够用了。