JWT实现用户认证
在介绍完JWT之后我们使用springboot整合JWT实现用户认证。
- 前后端分离使用JWT做用户认证(概述)
JWT实现认证的原理
服务器在生成一个JWT之后会将这个JWT会以Authorization : Bearer JWT 键值对的形式存放在 cookies里面发送到客户端机器,在客户端再次访问收到JWT保护的资源URL链接的时候,服务器会获取到cookies中存放的JWT信息,首先将Header进行反编码获取到加密的算法,在通过存放在服务器上的密匙对Header.Payload 这个字符串进行加密,比对JWT中的Signature和实际加密出来的结果是否一致,如果一致那么说明该JWT是合法有效的,认证成功,否则认证失败。
JWT实现用户认证的流程图
JWT的代码实现
代码说明:
代码中与JWT有关的内容如下
- config包中MvcConfig类配置生成一个JWT并配置了JWT拦截的URL,JwtProperties用于从配置文件中读取数据
- web包中UserController用于处理用户的登录,校验时生成JWT
- utils包中JwtUtils 用于对JWT的加密解析,RsaUtils用于从文件中读取公私钥
- interceptor包中LoginInterceptor实现对登录的拦截认证
- constants包中对JWT加密时要包含的内容
其余的是属于对数据库访问的相关内容,以及异常提示内容和捕捉异常信息内容。(在下面贴出代码)
引入关键依赖pom.xml
1
2
3
4
5
6
7
8
9
10
11 1 <dependency>
2 <groupId>commons-codec</groupId>
3 <artifactId>commons-codec</artifactId>
4 </dependency>
5 <dependency>
6 <groupId>io.jsonwebtoken</groupId>
7 <artifactId>jjwt</artifactId>
8 <version>0.9.0</version>
9 </dependency>
10
11
application.yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 1server:
2 port: 8888
3spring:
4 datasource:
5 url: jdbc:mysql://127.0.0.1:3306/demo?useSSL=false&serverTimezone=GMT%2B8
6 username: root
7 password: sasa
8 driver-class-name: com.mysql.cj.jdbc.Driver
9 jpa:
10 hibernate:
11 ddl-auto: update
12 show-sql: true
13jwt:
14 secret: demo@Login(Auth}*^31)&demo% # 登录校验的密钥
15 pubKeyPath: D:/coding/rsa/rsa.pub # 公钥地址
16 priKeyPath: D:/coding/rsa/rsa.pri # 私钥地址
17 expire: 30 # 过期时间,单位分钟
18 cookieName: DB_TOKEN
19
20
JwtUtils类 加密解密token封装的方法
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 1package com.demo.ssodemo.utils;
2
3import com.demo.ssodemo.constants.JwtConstans;
4import com.demo.ssodemo.constants.UserInfo;
5import io.jsonwebtoken.Claims;
6import io.jsonwebtoken.Jws;
7import io.jsonwebtoken.Jwts;
8import io.jsonwebtoken.SignatureAlgorithm;
9import org.joda.time.DateTime;
10
11import java.security.PrivateKey;
12import java.security.PublicKey;
13
14public class JwtUtils {
15 /**
16 * 私钥加密token
17 *
18 * @param userInfo 载荷中的数据
19 * @param privateKey 私钥
20 * @param expireMinutes 过期时间,单位秒
21 * @return
22 * @throws Exception
23 */
24 public static String generateToken(UserInfo userInfo, PrivateKey privateKey, int expireMinutes) throws Exception {
25 //JWT_KEY_ID为写入token中用户id
26 return Jwts.builder()
27 .claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
28 .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
29 .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
30 .signWith(SignatureAlgorithm.RS256, privateKey)
31 .compact();
32 }
33
34 /**
35 * 私钥加密token
36 *
37 * @param userInfo 载荷中的数据
38 * @param privateKey 私钥字节数组
39 * @param expireMinutes 过期时间,单位秒
40 * @return
41 * @throws Exception
42 */
43 public static String generateToken(UserInfo userInfo, byte[] privateKey, int expireMinutes) throws Exception {
44 return Jwts.builder()
45 .claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
46 .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
47 .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
48 .signWith(SignatureAlgorithm.RS256, RsaUtils.getPrivateKey(privateKey))
49 .compact();
50 }
51
52 /**
53 * 公钥解析token
54 *
55 * @param token 用户请求中的token
56 * @param publicKey 公钥
57 * @return
58 * @throws Exception
59 */
60 private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
61 return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
62 }
63
64 /**
65 * 公钥解析token
66 *
67 * @param token 用户请求中的token
68 * @param publicKey 公钥字节数组
69 * @return
70 * @throws Exception
71 */
72 private static Jws<Claims> parserToken(String token, byte[] publicKey) throws Exception {
73 return Jwts.parser().setSigningKey(RsaUtils.getPublicKey(publicKey))
74 .parseClaimsJws(token);
75 }
76
77 /**
78 * 获取token中的用户信息
79 *
80 * @param token 用户请求中的令牌
81 * @param publicKey 公钥
82 * @return 用户信息
83 * @throws Exception
84 */
85 public static UserInfo getInfoFromToken(String token, PublicKey publicKey) throws Exception {
86 Jws<Claims> claimsJws = parserToken(token, publicKey);
87 Claims body = claimsJws.getBody();
88 return new UserInfo(
89 ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
90 ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
91 );
92 }
93
94 /**
95 * 获取token中的用户信息
96 *
97 * @param token 用户请求中的令牌
98 * @param publicKey 公钥字节数组
99 * @return 用户信息
100 * @throws Exception
101 */
102 public static UserInfo getInfoFromToken(String token, byte[] publicKey) throws Exception {
103 Jws<Claims> claimsJws = parserToken(token, publicKey);
104 Claims body = claimsJws.getBody();
105 return new UserInfo(
106 ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
107 ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
108 );
109 }
110}
111
112
RsaUtils工具类
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 1package com.demo.ssodemo.utils;
2
3import java.io.File;
4import java.io.IOException;
5import java.nio.file.Files;
6import java.security.*;
7import java.security.spec.PKCS8EncodedKeySpec;
8import java.security.spec.X509EncodedKeySpec;
9
10public class RsaUtils {
11 /**
12 * 从文件中读取公钥
13 *
14 * @param filename 公钥保存路径,相对于classpath
15 * @return 公钥对象
16 * @throws Exception
17 */
18 public static PublicKey getPublicKey(String filename) throws Exception {
19 byte[] bytes = readFile(filename);
20 return getPublicKey(bytes);
21 }
22
23 /**
24 * 从文件中读取密钥
25 *
26 * @param filename 私钥保存路径,相对于classpath
27 * @return 私钥对象
28 * @throws Exception
29 */
30 public static PrivateKey getPrivateKey(String filename) throws Exception {
31 byte[] bytes = readFile(filename);
32 return getPrivateKey(bytes);
33 }
34
35 /**
36 * 获取公钥
37 *
38 * @param bytes 公钥的字节形式
39 * @return
40 * @throws Exception
41 */
42 public static PublicKey getPublicKey(byte[] bytes) throws Exception {
43 X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
44 KeyFactory factory = KeyFactory.getInstance("RSA");
45 return factory.generatePublic(spec);
46 }
47
48 /**
49 * 获取密钥
50 *
51 * @param bytes 私钥的字节形式
52 * @return
53 * @throws Exception
54 */
55 public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
56 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
57 KeyFactory factory = KeyFactory.getInstance("RSA");
58 return factory.generatePrivate(spec);
59 }
60
61 /**
62 * 根据密文,生成rsa公钥和私钥,并写入指定文件
63 *
64 * @param publicKeyFilename 公钥文件路径
65 * @param privateKeyFilename 私钥文件路径
66 * @param secret 生成密钥的密文
67 * @throws IOException
68 * @throws NoSuchAlgorithmException
69 */
70 public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
71 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
72 SecureRandom secureRandom = new SecureRandom(secret.getBytes());
73 keyPairGenerator.initialize(1024, secureRandom);
74 KeyPair keyPair = keyPairGenerator.genKeyPair();
75 // 获取公钥并写出
76 byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
77 writeFile(publicKeyFilename, publicKeyBytes);
78 // 获取私钥并写出
79 byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
80 writeFile(privateKeyFilename, privateKeyBytes);
81 }
82
83 /**
84 * 读取文件
85 * @param fileName
86 * @return
87 * @throws Exception
88 */
89 private static byte[] readFile(String fileName) throws Exception {
90 return Files.readAllBytes(new File(fileName).toPath());
91 }
92
93 /**
94 * 把二进制写入文件
95 * @param destPath
96 * @param bytes
97 * @throws IOException
98 */
99 private static void writeFile(String destPath, byte[] bytes) throws IOException {
100 File dest = new File(destPath);
101 if (!dest.exists()) {
102 dest.createNewFile();
103 }
104 Files.write(dest.toPath(), bytes);
105 }
106}
107
108
MvcConfig类,配置要的拦截或放行的URL,配合LoginInterceptor一起使用
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 1package com.demo.ssodemo.config;
2
3import com.demo.ssodemo.interceptor.LoginInterceptor;
4import com.fasterxml.jackson.databind.ObjectMapper;
5import com.fasterxml.jackson.databind.module.SimpleModule;
6import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
7import org.springframework.beans.factory.annotation.Autowired;
8import org.springframework.context.annotation.Bean;
9import org.springframework.context.annotation.Configuration;
10import org.springframework.data.redis.core.StringRedisTemplate;
11import org.springframework.http.converter.HttpMessageConverter;
12import org.springframework.http.converter.StringHttpMessageConverter;
13import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
14import org.springframework.web.servlet.config.annotation.EnableWebMvc;
15import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
16import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
17
18import java.math.BigInteger;
19import java.nio.charset.Charset;
20import java.util.ArrayList;
21import java.util.List;
22
23
24@Configuration
25@EnableWebMvc
26public class MvcConfig implements WebMvcConfigurer {
27
28 @Autowired
29 private JwtProperties jwtProperties;
30 @Autowired
31 private StringRedisTemplate stringRedisTemplate;
32
33 @Bean
34 public LoginInterceptor loginInterceptor() {
35 return new LoginInterceptor(jwtProperties,stringRedisTemplate);
36 }
37
38 @Override
39 public void addInterceptors(InterceptorRegistry registry) {
40 //配置放行的路径
41 List<String> excludePath = new ArrayList<>();
42 excludePath.add("/swagger-ui.html");
43 excludePath.add("/swagger-resources/**");
44 excludePath.add("/webjars/**");
45 excludePath.add("/login/**");
46 excludePath.add("/login"); //需要测试,暂时放行
47 registry.addInterceptor(loginInterceptor())//注册拦截器
48 //拦截所有
49 .addPathPatterns("/**").excludePathPatterns(excludePath);
50 }
51
52
53 @Override
54 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
55
56 MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
57 new MappingJackson2HttpMessageConverter();
58
59 ObjectMapper objectMapper = new ObjectMapper();
60 SimpleModule simpleModule = new SimpleModule();
61 simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
62 simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
63 simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
64 objectMapper.registerModule(simpleModule);
65 jackson2HttpMessageConverter.setObjectMapper(objectMapper);
66 converters.add(jackson2HttpMessageConverter);
67 converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
68 }
69
70}
71
72
73
LoginInterceptor,登录拦截器,拦截请求,如token失效或没有登录直接拦截回去
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 1package com.demo.ssodemo.interceptor;
2
3import com.demo.ssodemo.config.JwtProperties;
4import com.demo.ssodemo.constants.UserInfo;
5import com.demo.ssodemo.utils.CookieUtils;
6import com.demo.ssodemo.utils.JwtUtils;
7import org.apache.commons.lang3.StringUtils;
8import org.springframework.beans.factory.annotation.Autowired;
9import org.springframework.boot.context.properties.EnableConfigurationProperties;
10import org.springframework.data.redis.core.StringRedisTemplate;
11import org.springframework.http.HttpStatus;
12import org.springframework.web.servlet.ModelAndView;
13import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
14
15import javax.servlet.http.HttpServletRequest;
16import javax.servlet.http.HttpServletResponse;
17
18/**
19 * @Feature: 登录拦截器
20 */
21
22public class LoginInterceptor extends HandlerInterceptorAdapter {
23
24 private JwtProperties jwtProperties;
25 private StringRedisTemplate stringRedisTemplate;
26
27 /**
28 * 定义一个线程域,存放登录用户
29 */
30 private static final ThreadLocal<UserInfo> t1 = new ThreadLocal<>();
31
32 public LoginInterceptor(JwtProperties jwtProperties,StringRedisTemplate stringRedisTemplate) {
33 this.jwtProperties = jwtProperties;
34 this.stringRedisTemplate = stringRedisTemplate;
35 }
36
37 /**
38 * 在业务处理器处理请求之前被调用
39 * 如果返回false
40 * 则从当前的拦截器往回执行所有拦截器的afterCompletion(),再退出拦截器链
41 * 如果返回true
42 * 执行下一个拦截器,直到所有拦截器都执行完毕
43 * 再执行被拦截的Controller
44 * 然后进入拦截器链
45 * 从最后一个拦截器往回执行所有的postHandle()
46 * @param request
47 * @param response
48 * @param handler
49 * @return
50 * @throws Exception
51 */
52 @Override
53 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
54 //1.查询token
55 String token = CookieUtils.getCookieValue(request,jwtProperties.getCookieName());
56 if (StringUtils.isBlank(token)){
57 //2.未登录,返回401
58 response.setStatus(HttpStatus.UNAUTHORIZED.value());
59 return false;
60 }
61
62 //3.有token,查询用户信息
63 try{
64 //4.解析成功,说明已经登录
65 UserInfo userInfo = JwtUtils.getInfoFromToken(token,jwtProperties.getPublicKey());
66 //5.放入线程域
67 t1.set(userInfo);
68 return true;
69 }catch (Exception e){
70 //6.抛出异常,证明未登录,返回401
71 response.setStatus(HttpStatus.UNAUTHORIZED.value());
72 return false;
73 }
74 }
75
76 /**
77 * 在业务处理器处理请求执行完成后,生成视图之前执行的动作
78 * 可在modelAndView中加入数据,比如当前时间
79 * @param request
80 * @param response
81 * @param handler
82 * @param modelAndView
83 * @throws Exception
84 */
85 @Override
86 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
87 super.postHandle(request, response, handler, modelAndView);
88 }
89
90 /**
91 * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等
92 * 当有拦截器抛出异常时,会从当前拦截器往回执行所有的拦截器的afterCompletion()
93 * @param request
94 * @param response
95 * @param handler
96 * @param ex
97 * @throws Exception
98 */
99 @Override
100 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
101 t1.remove();
102 }
103 //返回用户信息
104 public static UserInfo getLoginUser(){
105 return t1.get();
106 }
107}
108
109
110
JwtProperties,用于从配置文件中读取数据
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 1package com.demo.ssodemo.config;
2
3import com.demo.ssodemo.utils.RsaUtils;
4import lombok.Data;
5import org.springframework.beans.factory.annotation.Value;
6import org.springframework.boot.context.properties.ConfigurationProperties;
7import org.springframework.context.annotation.Configuration;
8
9import javax.annotation.PostConstruct;
10import java.io.File;
11import java.security.PrivateKey;
12import java.security.PublicKey;
13
14@Data
15//@ConfigurationProperties(prefix = "jwt")
16@Configuration
17public class JwtProperties {
18 @Value("${jwt.secret}")
19 private String secret; // 密钥
20 @Value("${jwt.pubKeyPath}")
21 private String pubKeyPath;// 公钥
22 @Value("${jwt.priKeyPath}")
23 private String priKeyPath;// 私钥
24 @Value("${jwt.expire}")
25 private int expire;// token过期时间
26 @Value("${jwt.cookieName}")
27 private String cookieName;
28
29 private PublicKey publicKey;
30 private PrivateKey privateKey;
31
32 //对象一旦实例化后,就应该读取公钥和私钥
33 @PostConstruct //构造函数执行完成后执行
34 public void init() throws Exception {
35 //公钥私钥不存在,先生成
36 File pubkeyPath = new File(pubKeyPath);
37 File prikeyPath = new File(priKeyPath);
38 if (!pubkeyPath.exists() || !prikeyPath.exists()) {
39 RsaUtils.generateKey(pubKeyPath, priKeyPath, secret);
40 }
41 //读取公钥和私钥
42 this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
43 this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
44 }
45
46}
47
48
49
constants包下JwtConstans为加密token时需要写入的用户的基本信息,UserInfo用于解密后存放用户信息
JwtConstans
1
2
3
4
5
6 1public abstract class JwtConstans {
2 public static final String JWT_KEY_ID = "id";
3 public static final String JWT_KEY_USER_NAME = "username";
4}
5
6
UserInfo
1
2
3
4
5
6
7
8
9
10
11 1@Data
2@AllArgsConstructor
3@NoArgsConstructor
4public class UserInfo {
5
6 private Long id;
7
8 private String username;
9}
10
11
我们需要使用到的工具类和拦截器以完成,我们编写Controller,service来测试
Controller
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 1package com.demo.ssodemo.web;
2
3import com.demo.ssodemo.config.JwtProperties;
4import com.demo.ssodemo.constants.UserInfo;
5import com.demo.ssodemo.enums.ExceptionEnum;
6import com.demo.ssodemo.exception.CustomException;
7import com.demo.ssodemo.interceptor.LoginInterceptor;
8import com.demo.ssodemo.service.UserService;
9import com.demo.ssodemo.utils.CookieUtils;
10import com.demo.ssodemo.utils.JwtUtils;
11import org.springframework.beans.factory.annotation.Autowired;
12import org.springframework.http.HttpStatus;
13import org.springframework.http.ResponseEntity;
14import org.springframework.web.bind.annotation.*;
15
16import javax.servlet.http.HttpServletRequest;
17import javax.servlet.http.HttpServletResponse;
18
19
20@RestController
21public class UserController {
22 @Autowired
23 private UserService userService;
24 @Autowired
25 private JwtProperties prop;
26
27
28 @PostMapping("/login")
29 public ResponseEntity<Void> login(
30 @RequestParam("username") String username,
31 @RequestParam("password") String password,
32 HttpServletResponse response, HttpServletRequest request) {
33 //登录
34 String token = userService.login(username, password);
35 CookieUtils.newBuilder(response).httpOnly().request(request)
36 .build(prop.getCookieName(), token);
37 return ResponseEntity.status(HttpStatus.OK).build();
38 }
39
40 /**
41 * 校验用户登录状态
42 *
43 * @return
44 */
45 @GetMapping("verify")
46 public ResponseEntity<UserInfo> verify(
47 @CookieValue("DB_TOKEN") String token,
48 HttpServletResponse response, HttpServletRequest request
49 ) {
50 try {
51 //解析token
52 //已经拦截解析,取值即可
53
54 UserInfo info = LoginInterceptor.getLoginUser();
55
56 //刷新token,重新生成token
57 String newToken = JwtUtils.generateToken(info, prop.getPrivateKey(), prop.getExpire());
58 //写入token中
59 CookieUtils.newBuilder(response).httpOnly().request(request)
60 .build(prop.getCookieName(),newToken);
61
62 //已登录,返回用户信息
63 return ResponseEntity.ok(info);
64 } catch (Exception e) {
65 //token已过期,或者token无效
66 throw new CustomException(ExceptionEnum.UNAUTHORIZED);
67 }
68 }
69}
70
71
UserServiceImpl 实现
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 1package com.demo.ssodemo.service.impl;
2
3import com.demo.ssodemo.config.JwtProperties;
4import com.demo.ssodemo.constants.UserInfo;
5import com.demo.ssodemo.enums.ExceptionEnum;
6import com.demo.ssodemo.exception.CustomException;
7import com.demo.ssodemo.pojo.User;
8import com.demo.ssodemo.repository.UserRepository;
9import com.demo.ssodemo.service.UserService;
10import com.demo.ssodemo.utils.CodecUtils;
11import com.demo.ssodemo.utils.JwtUtils;
12import lombok.extern.slf4j.Slf4j;
13import org.apache.commons.lang3.StringUtils;
14import org.springframework.beans.factory.annotation.Autowired;
15import org.springframework.data.domain.Example;
16import org.springframework.data.redis.core.StringRedisTemplate;
17import org.springframework.stereotype.Service;
18
19import java.util.Optional;
20
21@Slf4j
22@Service
23public class UserServiceImpl implements UserService {
24 @Autowired
25 private UserRepository goodsRepository;
26 @Autowired
27 private JwtProperties prop;
28
29
30 private User queruUserByUsernameAndPassword(String username, String password) {
31 //查询用户
32 User record = new User();
33 record.setUsername(username);
34 Optional<User> user = goodsRepository.findOne(Example.of(record));
35 //校验
36 if (!user.isPresent()) {
37 throw new CustomException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
38 }
39 //校验密码
40 String pwd = CodecUtils.md5Hex(password, user.get().getSalt());
41 if (!StringUtils.equals(user.get().getPassword(), pwd)) {
42 throw new CustomException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
43 }
44 return user.get();
45 }
46
47 @Override
48 public String login(String username, String password) {
49 try {
50
51 //根据用户名和密码查询
52 User user = queruUserByUsernameAndPassword(username, password);
53 //判断user
54 if (user == null) {
55 throw new CustomException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
56 }
57 //jwtUtils生成jwt类型的token
58 //生成token
59 String token = JwtUtils.generateToken(new UserInfo(user.getId(), username), prop.getPrivateKey(), prop.getExpire());
60 return token;
61 } catch (Exception e) {
62 log.error("[登陆中心] 用户名或密码有误,用户名称{}", username, e);
63 throw new CustomException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
64 }
65 }
66}
67
68
69
接下来我们在Postman工具中测试,可以看到成功写入cookie中,然后我们每次请求都会携带这个token,我们只需要拦截下来校验即可,就可以实现单点登录一样的效果。
我们请求校验用户登录状态的方法,或请求我们网站其他页面时,看看我们的拦截器是否有效果,是否能成功解析出用户信息。
可以看到我们的请求已经被拦截,并且我们使用工具类获取到客户端携带的cookie中我们设置的token,并成功解析,放入线程域中,以便我们在校验用户状态的方法中获取用户信息,在获取信息后我们刷新一遍token并重新写入cookie中,最后返回用户信息。
总结:
在我们登录一个网站之后,下一次打开网页的时候可能显示的还是登录的状态,不需要再次进行登录操作,通过JWT就可以实现这样一个用户认证的功能。当然使用Session可以实现这个功能,但是使用Session的同时也会增加服务器的存储压力,而JWT是将存储的压力分布到各个客户端机器上,从而减轻服务器的压力。
其实业务系统代码非常简单,主要是用了一个拦截器,拦截 http 请求,提取出 token 向 sso 认证中心验证 token 是否有效,有效放行,否则返回错误给前端。