Spring Boot整合JWT实现用户认证

释放双眼,带上耳机,听听看~!

JWT实现用户认证

在介绍完JWT之后我们使用springboot整合JWT实现用户认证。

  • 前后端分离使用JWT做用户认证(概述)

JWT实现认证的原理


​服务器在生成一个JWT之后会将这个JWT会以Authorization : Bearer JWT 键值对的形式存放在 cookies里面发送到客户端机器,在客户端再次访问收到JWT保护的资源URL链接的时候,服务器会获取到cookies中存放的JWT信息,首先将Header进行反编码获取到加密的算法,在通过存放在服务器上的密匙对Header.Payload 这个字符串进行加密,比对JWT中的Signature和实际加密出来的结果是否一致,如果一致那么说明该JWT是合法有效的,认证成功,否则认证失败。


JWT实现用户认证的流程图

Spring Boot整合JWT实现用户认证

JWT的代码实现

代码说明:

代码中与JWT有关的内容如下

  1. config包中MvcConfig类配置生成一个JWT并配置了JWT拦截的URL,JwtProperties用于从配置文件中读取数据
  2. web包中UserController用于处理用户的登录,校验时生成JWT
  3. utils包中JwtUtils 用于对JWT的加密解析,RsaUtils用于从文件中读取公私钥
  4. interceptor包中LoginInterceptor实现对登录的拦截认证
  5. constants包中对JWT加密时要包含的内容

其余的是属于对数据库访问的相关内容,以及异常提示内容和捕捉异常信息内容。(在下面贴出代码)

Spring Boot整合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,我们只需要拦截下来校验即可,就可以实现单点登录一样的效果。

Spring Boot整合JWT实现用户认证

我们请求校验用户登录状态的方法,或请求我们网站其他页面时,看看我们的拦截器是否有效果,是否能成功解析出用户信息。

Spring Boot整合JWT实现用户认证

可以看到我们的请求已经被拦截,并且我们使用工具类获取到客户端携带的cookie中我们设置的token,并成功解析,放入线程域中,以便我们在校验用户状态的方法中获取用户信息,在获取信息后我们刷新一遍token并重新写入cookie中,最后返回用户信息。

Spring Boot整合JWT实现用户认证Spring Boot整合JWT实现用户认证

总结:

​ 在我们登录一个网站之后,下一次打开网页的时候可能显示的还是登录的状态,不需要再次进行登录操作,通过JWT就可以实现这样一个用户认证的功能。当然使用Session可以实现这个功能,但是使用Session的同时也会增加服务器的存储压力,而JWT是将存储的压力分布到各个客户端机器上,从而减轻服务器的压力。

​ 其实业务系统代码非常简单,主要是用了一个拦截器,拦截 http 请求,提取出 token 向 sso 认证中心验证 token 是否有效,有效放行,否则返回错误给前端。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C/C++内存泄漏及检测

2022-1-11 12:36:11

安全漏洞

从 Stack Overflow 复制代码导致 GitHub 项目漏洞多

2019-10-27 11:12:22

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索