Springboot整合SpringSecurity-jwt

前言

关于系统最终想实现的功能:使用token来实现登录校验,用户登录后拿到token,然后将token放入httpHeader,之后每次接口请求都携带token,验证成功才可进行正常访问流程

储备知识

token与 jwt (JSON Web Token)介绍

JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

1
2
3
4
5
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}

JWT 的数据结构

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

JWT 的三个部分依次如下。

1
2
3
4
5
6
7
8
9
10
11
12
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJjcWMiLCJjcmVhdGVkIjoxNjk0Njc1MjM2ODE2LCJleHAiOjIxMjY2NzUyMzZ9.Fi1Th2VOXpTASN3sRZmuvy4rNsGtFkaxIrkx85-I29PVYnOfxTc_jl2XwnrELiWCP4tHxnrUqUjkHyoHxrPNRA"
},
"msg": "小朋友,登陆成功!"
}


eyJhbGciOiJIUzUxMiJ9.
eyJzdWIiOiJjcWMiLCJjcmVhdGVkIjoxNjk0Njc1MjM2ODE2LCJleHAiOjIxMjY2NzUyMzZ9.
Fi1Th2VOXpTASN3sRZmuvy4rNsGtFkaxIrkx85-I29PVYnOfxTc_jl2XwnrELiWCP4tHxnrUqUjkHyoHxrPNRA
  • Header(头部)
  • Payload(负载)
  • Signature(签名)

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

最后,将上面的 JSON 对象使用 Base64URL 算法转成字符串。

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

这个 JSON 对象也要使用 Base64URL 算法转成字符串。

Signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

1
HMACSHA256(  base64UrlEncode(header) + "." +  base64UrlEncode(payload),  secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

JWT 工具类

有很多常见的工具类,我这边用的是这个

1
2
3
4
5
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

spring security简介

介绍的文章一抓一大把,这边主要说一下他的几个核心东西

用户认证(Authentication)

是验证您的身份的凭据(例如用户名/用户ID和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。

用户授权(Authorization)

发生在 **Authentication(认证)**之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。

过滤器链

img

核心组件

  • AuthenticationManager
  • SecurityContextHolder
  • PasswordEncoder
  • UserDetails
  • UserDetailsService
  • BasicAuthenticationFilter
  • AuthenticationEntryPoint

登录流程图

img

集成流程(代码部分,不看可以跳过)

gitee地址: https://gitee.com/cactuscode/springboot-spring-security-jwt.git

集成spring security

数据库

需要建两个表

user

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
/*
Navicat Premium Data Transfer

Source Server : 192.168.55.101
Source Server Type : MySQL
Source Server Version : 50734
Source Host : 192.168.55.101:3306
Source Schema : cqc

Target Server Type : MySQL
Target Server Version : 50734
File Encoding : 65001

Date: 14/09/2023 15:15:09
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`role_id` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'cqc', '$2a$10$jOpeMlGJ7ZRE.8mebeVQ4uPDS89wdBmLTmqXcfmw8ncRzsCjY7KlC', 2);

SET FOREIGN_KEY_CHECKS = 1;

role

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
/*
Navicat Premium Data Transfer

Source Server : 192.168.55.101
Source Server Type : MySQL
Source Server Version : 50734
Source Host : 192.168.55.101:3306
Source Schema : cqc

Target Server Type : MySQL
Target Server Version : 50734
File Encoding : 65001

Date: 14/09/2023 15:15:27
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`role_id` int(11) NOT NULL,
`role_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_ADMIN', 'ROLE_ADMIN');
INSERT INTO `role` VALUES (2, 'ROLE_NORMAL', 'ROLE_NORMAL');

SET FOREIGN_KEY_CHECKS = 1;

maven依赖

springboot版本 2.7.5

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
<!--springboot-security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--springboot-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>


<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.21</version>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<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>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server:
port: 8001

spring:
datasource:
url: jdbc:mysql://192.168.55.101:3306/cqc?characterEncoding=utf-8&autoReconnect=true&useSSL=false
username: root
password: Gmmysql_300098
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
mapper-locations: classpath:mapper/*.xml


logging:
level:
com.chang.springsecurity.mapper: debug

User

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
package com.cactus.springsecurity.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:18
* @Description:
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements UserDetails {

private String uid;

private String username;
private String password;
private List<Role> roles;

@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
if (roles == null) {
return authorities;
}
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
}
return authorities;
}


@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

}

Role

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.cactus.springsecurity.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:21
* @Description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
private Long id;
private String roleName;
// 角色描述
private String description;
}

Result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.cactus.springsecurity.entity.response;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:53
* @Description:
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private int code;
private Object data;
private String msg;

}

UserMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.cactus.springsecurity.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cactus.springsecurity.entity.User;

import java.util.List;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:32
* @Description: userMapper
*/
public interface UserMapper extends BaseMapper<User> {
List<User> findAll();

User findByUsername(String username);

int save(User user);
}

UserService

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
package com.cactus.springsecurity.service;

import com.cactus.springsecurity.entity.User;
import com.cactus.springsecurity.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 12:00
* @Description: 用户
*/
@Service
public class UserService implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

User user = userMapper.findByUsername(s);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return user;
}
}

JwtConfigure

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
package com.cactus.springsecurity.config;

import cn.hutool.crypto.digest.DigestUtil;


/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:52
* @Description: jwt常量配置
*/
public class JwtConfigure {

public static final long EXPIRATION_TIME = 432_000_000; // 5天(以毫秒ms计)
public static final String SECRET = "SystemSecret+d7d8825f2b61d50a48508b17b88b4e24+d7d8825f2b61d50a48508b17b88b4e24+d7d8825f2b61d50a48508b17b88b4e24"; // JWT密码 不够长会报错
public static final String TOKEN_PREFIX = "Bearer"; // Token前缀
public static final String HEADER_STRING = "Authorization"; // 存放Token的Header Key


public static void main(String[] args) {
System.out.println(DigestUtil.md5Hex("caiqichang"));
}
}



JwtTokenUtil

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
package com.cactus.springsecurity.utils;

import com.cactus.springsecurity.config.JwtConfigure;
import com.cactus.springsecurity.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:49
* @Description: jwt工具
*/
@Component
public class JwtTokenUtil {

private static final long serialVersionUID = -5625635588908941275L;

private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";

public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}


private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey( JwtConfigure.SECRET )
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}

private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + JwtConfigure.EXPIRATION_TIME * 1000);
}

private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}

private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}

public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}

String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, JwtConfigure.SECRET )
.compact();
}

public Boolean canTokenBeRefreshed(String token) {
return !isTokenExpired(token);
}

public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}

public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
final String username = getUsernameFromToken(token);
return (
username.equals(user.getUsername())
&& !isTokenExpired(token)
);
}

}

MyAccessDeniedHandler

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
package com.cactus.springsecurity.handler;

import cn.hutool.json.JSONUtil;
import com.cactus.springsecurity.entity.response.Result;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 12:06
* @Description: 没有权限处理器
*/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html;charset=utf-8");
httpServletResponse.getWriter().write(JSONUtil.toJsonStr(new Result(111,null,"小朋友,你没有访问权限!")));
}
}

MyAuthenticationEntryPoint

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
package com.cactus.springsecurity.handler;

import cn.hutool.json.JSONUtil;
import com.cactus.springsecurity.entity.response.Result;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 12:49
* @Description: authenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
*/
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html;charset=utf-8");
if (e instanceof BadCredentialsException) {
httpServletResponse.getWriter().write(JSONUtil.toJsonStr(new Result(401,null,"小朋友,账号或者密码错误")));
}else if (e instanceof InsufficientAuthenticationException) {
httpServletResponse.getWriter().write(JSONUtil.toJsonStr(new Result(402,null,"小朋友,你的token已过期或者不存在!")));
}
else {
httpServletResponse.getWriter().write(JSONUtil.toJsonStr(new Result(500,null,"系统繁忙!")));
}

}
}

JwtTokenFilter

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
package com.cactus.springsecurity.filter;

import com.cactus.springsecurity.config.JwtConfigure;
import com.cactus.springsecurity.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 12:54
* @Description: jwt过滤器
*/
@Component
public class JwtTokenFilter extends OncePerRequestFilter {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

// 获取存放Token的Header Key "Authorization"
String authHeader = httpServletRequest.getHeader(JwtConfigure.HEADER_STRING);
// Token前缀为Bearer
if (authHeader != null && authHeader.startsWith(JwtConfigure.TOKEN_PREFIX)) {
// 去除前缀Bearer取得完整的token
final String authToken = authHeader.substring(JwtConfigure.TOKEN_PREFIX.length());
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}

MyWebSecurityConfigure

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
package com.cactus.springsecurity.config;

import cn.hutool.crypto.digest.DigestUtil;


/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:52
* @Description: jwt常量配置
*/
public class JwtConfigure {

public static final long EXPIRATION_TIME = 432_000_000; // 5天(以毫秒ms计)
public static final String SECRET = "SystemSecret+d7d8825f2b61d50a48508b17b88b4e24+d7d8825f2b61d50a48508b17b88b4e24+d7d8825f2b61d50a48508b17b88b4e24"; // JWT密码 不够长会报错
public static final String TOKEN_PREFIX = "Bearer"; // Token前缀
public static final String HEADER_STRING = "Authorization"; // 存放Token的Header Key


public static void main(String[] args) {
System.out.println(DigestUtil.md5Hex("caiqichang"));
}
}



AuthService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.cactus.springsecurity.service;

import com.cactus.springsecurity.entity.User;
import com.cactus.springsecurity.entity.response.Result;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:52
* @Description:
*/
public interface AuthService {
Result register(User userToAdd );
String login( String username, String password );
}

AuthServiceImpl

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
package com.cactus.springsecurity.service.impl;

import com.cactus.springsecurity.entity.User;
import com.cactus.springsecurity.entity.response.Result;
import com.cactus.springsecurity.mapper.UserMapper;
import com.cactus.springsecurity.service.AuthService;
import com.cactus.springsecurity.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:52
* @Description:
*/
@Service
public class AuthServiceImpl implements AuthService {

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Autowired
private UserMapper userMapper;

// 登录
@Override
public String login( String username, String password ) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );
final Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
final UserDetails userDetails = userDetailsService.loadUserByUsername( username );
final String token = jwtTokenUtil.generateToken(userDetails);
return token;
}

// 注册
@Override
public Result register(User userToAdd ) {
final String username = userToAdd.getUsername();
if( userMapper.findByUsername(username)!=null ) {
return new Result(400,null,"账号已存在!");
}
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
final String rawPassword = userToAdd.getPassword();
userToAdd.setPassword( encoder.encode(rawPassword) );
int i = userMapper.save(userToAdd);
if (i>=0){
User user = userMapper.findByUsername(userToAdd.getUsername());
user.setPassword("******");
return new Result(200,user,"注册成功!");
}
else {
return new Result(406,null,"发生未知错误,注册失败!");
}
}
}

JwtAuthController

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
package com.cactus.springsecurity.config;

import cn.hutool.crypto.digest.DigestUtil;


/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:52
* @Description: jwt常量配置
*/
public class JwtConfigure {

public static final long EXPIRATION_TIME = 432_000_000; // 5天(以毫秒ms计)
public static final String SECRET = "SystemSecret+d7d8825f2b61d50a48508b17b88b4e24+d7d8825f2b61d50a48508b17b88b4e24+d7d8825f2b61d50a48508b17b88b4e24"; // JWT密码 不够长会报错
public static final String TOKEN_PREFIX = "Bearer"; // Token前缀
public static final String HEADER_STRING = "Authorization"; // 存放Token的Header Key


public static void main(String[] args) {
System.out.println(DigestUtil.md5Hex("caiqichang"));
}
}



TestController

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
package com.cactus.springsecurity.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:52
* @Description:
*/
@RestController
public class TestController {

// 测试普通权限
@PreAuthorize("hasAuthority('ROLE_NORMAL')")
@RequestMapping( value="/normal/test", method = RequestMethod.GET )
public String test1() {
return "ROLE_NORMAL /normal/test接口调用成功!";
}

// 测试管理员权限
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@RequestMapping( value = "/admin/test", method = RequestMethod.GET )
public String test2() {
return "ROLE_ADMIN /admin/test接口调用成功!";
}
}

SpringsecurityJwtApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.cactus.springsecurity;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @Author: cactus
* @CreateDate: 2023/9/14 11:52
* @Description: 主入口
*/
@SpringBootApplication
@MapperScan(basePackages = "com.cactus.springsecurity.mapper")
public class SpringsecurityJwtApplication {

public static void main(String[] args) {
SpringApplication.run(SpringsecurityJwtApplication.class, args);
}

}

测试

测试注册

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
POST http://192.168.77.142:8001/authentication/register

{
"uid": "1",
"username": "cqc1",
"password": "cqc1"
}

result ======>
{
"code": 200,
"data": {
"uid": "2",
"username": "cqc1",
"password": "******",
"roles": [
{
"id": 2,
"roleName": "ROLE_NORMAL",
"description": "ROLE_NORMAL"
}
],
"enabled": true,
"credentialsNonExpired": true,
"accountNonExpired": true,
"accountNonLocked": true
},
"msg": "注册成功!"
}

image-20230914155159127

测试登录

1
2
3
4
5
6
7
8
9
POST http://192.168.77.142:8001/authentication/login?username=cqc1&password=cqc1

{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJjcWMxIiwiY3JlYXRlZCI6MTY5NDY3ODAwNDI1NSwiZXhwIjoyMTI2Njc4MDA0fQ.n5BinuMFAF3cLplCpv-M7VdjWoKfW3zAKGFlaNei79DUiZ7GVbExZ-gX01-Iw7Qa0ltIDki5IY1PjVqarOJz3w"
},
"msg": "小朋友,登陆成功!"
}

image-20230914155340135

测试权限接口

成功状态

1
2
3
4
GET http://192.168.77.142:8001/normal/test

Headers ==>
Authorization:BearereyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJjcWMxIiwiY3JlYXRlZCI6MTY5NDY3ODAwNDI1NSwiZXhwIjoyMTI2Njc4MDA0fQ.n5BinuMFAF3cLplCpv-M7VdjWoKfW3zAKGFlaNei79DUiZ7GVbExZ-gX01-Iw7Qa0ltIDki5IY1PjVqarOJz3w

image-20230914155432350

失败状态

1
2
3
4
GET http://192.168.77.142:8001/admin/test

Headers ==>
Authorization:BearereyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJjcWMxIiwiY3JlYXRlZCI6MTY5NDY3ODAwNDI1NSwiZXhwIjoyMTI2Njc4MDA0fQ.n5BinuMFAF3cLplCpv-M7VdjWoKfW3zAKGFlaNei79DUiZ7GVbExZ-gX01-Iw7Qa0ltIDki5IY1PjVqarOJz3w

image-20230914155543008


Springboot整合SpringSecurity-jwt
https://cai-qichang.github.io/2023/09/14/Springboot整合SpringSecurity-jwt/
作者
caiqichang
发布于
2023年9月14日
许可协议
BY-蔡奇倡