SpringBoot整合Jwt实现前后端分离代码:下载地址一
SpringBoot整合Jwt实现前后端分离代码:下载地址二
CREATE TABLE `tb_user` (
`id` int(45) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1002 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.13version>
dependency>
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.10.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.18version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.22version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.4version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.2version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.4version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-autoconfigureartifactId>
<version>2.1.2version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.2.5version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.2.3.RELEASEversion>
dependency>
server:
port: 80
servlet:
context-path: /jd
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_test?useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
mybatis:
config-location: classpath:mybatis.xml
#Mapper.xml所在的位置
mapper-locations: classpath*:mapper/*.xml
#Entity扫描的model包
type-aliases-package: com.hc.bean
logging:
config: classpath:logback.xml
<configuration>
<settings>
<setting name="logImpl" value="SLF4J"/>
<setting name="mapUnderscoreToCamelCase" value="true" />
settings>
configuration>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<jmxConfigurator />
<property name="log_dir" value="./logs" />
<property name="maxHistory" value="30" />
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n
pattern>
encoder>
appender>
<root>
<level value="info" />
<appender-ref ref="console" />
root>
configuration>
@Slf4j
public class Base64Util {
private static final String charset = "utf-8";
// 解密
public static String decode(String data) {
try {
if (null == data) {
return null;
}
return new String(Base64.decodeBase64(data.getBytes(charset)), charset);
} catch (UnsupportedEncodingException e) {
log.error(String.format("字符串:%s,解密异常", data), e);
}
return null;
}
// 加密
public static String encode(String data) {
try {
if (null == data) {
return null;
}
return new String(Base64.encodeBase64(data.getBytes(charset)), charset);
} catch (UnsupportedEncodingException e) {
log.error(String.format("字符串:%s,加密异常", data), e);
}
return null;
}
}
public interface JwtConst {
// 代表这个JWT的接收对象
String clientId = "098f6bcd4621d373cade4e832627b4f6";
// 密钥, 经过Base64加密, 可自行替换
String secret = "MDk4ZjZiY2Q0NjIxZD";
// JWT的签发主体,存入issuer
String name = "restapiuser";
// 过期时间,单位毫秒
long expires = 60000;
// 从客户端传递过来的token的名称
String token = "token";
}
@Slf4j
public class JwtTokenUtil {
// 解析jwt
public static Claims parseJWT(String jsonWebToken, String base64Security) {
try {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
.parseClaimsJws(jsonWebToken).getBody();
return claims;
} catch (ExpiredJwtException eje) {
log.error("===== Token过期 =====", eje);
throw new CustomException(ResultCode.PERMISSION_TOKEN_EXPIRED);
} catch (Exception e){
log.error("===== token解析异常 =====", e);
throw new CustomException(ResultCode.PERMISSION_TOKEN_INVALID);
}
}
// 构建jwt
public static String createJWT(String userId, String username, String role) {
try {
// 使用HS256加密算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//生成签名密钥
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(JwtConst.secret);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//userId是重要信息,进行加密下
String encryUserId = Base64Util.encode(userId);
//添加构成JWT的参数
JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
// 可以将基本不重要的对象信息放到claims
.claim("role", role)
.claim("userId", encryUserId)
.setSubject(username) // 代表这个JWT的主体,即它的所有人
.setIssuer(JwtConst.clientId) // 代表这个JWT的签发主体
.setIssuedAt(new Date()) // 是一个时间戳,代表这个JWT的签发时间
.setAudience(JwtConst.name) // 代表这个JWT的接收对象
.signWith(signatureAlgorithm, signingKey);
//添加Token过期时间
long TTLMillis = JwtConst.expires;
if (TTLMillis >= 0) {
long expMillis = nowMillis + TTLMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp) // 是一个时间戳,代表这个JWT的过期时间;
.setNotBefore(now); // 是一个时间戳,代表这个JWT生效的开始时间
}
//生成JWT
return builder.compact();
} catch (Exception e) {
log.error("签名失败", e);
throw new CustomException(ResultCode.PERMISSION_SIGNATURE_ERROR);
}
}
// 从token中获取用户名
public static String getUsername(String token){
return parseJWT(token, JwtConst.secret).getSubject();
}
// 从token中获取用户ID
public static String getUserId(String token){
String encryUserId = parseJWT(token, JwtConst.secret).get("userId").toString();
return Base64Util.decode(encryUserId);
}
// 是否已过期
public static boolean isExpiration(String token) {
return parseJWT(token, JwtConst.secret).getExpiration().before(new Date());
}
}
@Data
@ToString
public class Result<T> {
private Integer code;
private String msg;
private T data;
public Result(String msg) {
this.msg = msg;
}
public Result(ResultCode resultCode) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
}
public Result(ResultCode resultCode, T data) {
this(resultCode);
this.data = data;
}
public static Result SUCCESS() {
return new Result(ResultCode.SUCCESS);
}
public static <T> Result SUCCESS(T data) {
return new Result(ResultCode.SUCCESS, data);
}
public static Result FAIL() {
return new Result(ResultCode.FAIL);
}
public static Result FAIL(String msg) {
return new Result(msg);
}
}
public enum ResultCode {
/* 成功状态码 */
SUCCESS(0,"操作成功!"),
/* 错误状态码 */
FAIL(-1,"操作失败!"),
/* 参数错误:10001-19999 */
PARAM_IS_INVALID(10001, "参数无效"),
/* 用户错误:20001-29999*/
USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"),
/* 系统错误:40001-49999 */
SYSTEM_INNER_ERROR(40001, "系统繁忙,请稍后重试"),
/* 权限错误:70001-79999 */
PERMISSION_TOKEN_EXPIRED(70004, "token已过期"),
PERMISSION_TOKEN_INVALID(70006, "无效token"),
PERMISSION_SIGNATURE_ERROR(70007, "签名失败");
//操作代码
int code;
//提示信息
String msg;
ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
public class CustomException extends RuntimeException {
//错误代码
ResultCode resultCode;
public CustomException(ResultCode resultCode){
super(resultCode.getMsg());
this.resultCode = resultCode;
}
public CustomException(ResultCode resultCode, Object... args){
super(resultCode.getMsg());
String message = MessageFormat.format(resultCode.getMsg(), args);
resultCode.setMsg(message);
this.resultCode = resultCode;
}
public ResultCode getResultCode(){
return resultCode;
}
}
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 处理自定义异常
@ExceptionHandler(CustomException.class)
public Result handleException(CustomException e) {
// 打印异常信息
log.error("### 异常信息:{} ###", e.getMessage());
return new Result(e.getResultCode());
}
// 参数错误异常
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
public Result handleException(Exception e) {
if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
BindingResult result = validException.getBindingResult();
StringBuffer errorMsg = new StringBuffer();
if (result.hasErrors()) {
List<ObjectError> errors = result.getAllErrors();
errors.forEach(p ->{
FieldError fieldError = (FieldError) p;
errorMsg.append(fieldError.getDefaultMessage()).append(",");
log.error("### 请求参数错误:{"+fieldError.getObjectName()+"},field{"+fieldError.getField()+ "},errorMessage{"+fieldError.getDefaultMessage()+"}"); });
}
} else if (e instanceof BindException) {
BindException bindException = (BindException)e;
if (bindException.hasErrors()) {
log.error("### 请求参数错误: {}", bindException.getAllErrors());
}
}
return new Result(ResultCode.PARAM_IS_INVALID);
}
// 处理所有不可知的异常
@ExceptionHandler(Exception.class)
public Result handleOtherException(Exception e){
//打印异常堆栈信息
e.printStackTrace();
// 打印异常信息
log.error("### 不可知的异常:{} ###", e.getMessage());
return new Result(ResultCode.SYSTEM_INNER_ERROR);
}
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginToken {
boolean required() default true;
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查是否有passtoken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
log.info("当前所访问的资源不需要登录");
return true;
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(LoginToken.class)) {
LoginToken loginToken = method.getAnnotation(LoginToken.class);
if (loginToken.required()) {
// 从 http 请求头中取出 token
String authHeaderKey = JwtConst.token;
String token = request.getHeader(authHeaderKey);
if (token == null || token.length() == 0) {
token = request.getParameter(authHeaderKey);
}
if (token == null || token.length() == 0) {
log.info("用户未登录,请先登录");
throw new CustomException(ResultCode.USER_NOT_LOGGED_IN);
}
String userId = JwtTokenUtil.getUserId(token);
User user = userService.selectByPrimaryKey(Integer.parseInt(userId));
if (user == null) {
log.info("用户不存在,请重新登录");
throw new RuntimeException("用户不存在,请重新登录");
}
log.info("用户存在,登录成功");
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {//跨域请求
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**"); //用于设置拦截器的过滤路径规则
// 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
}
@Bean
public JwtInterceptor authenticationInterceptor() {
return new JwtInterceptor();
}
}
@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = -8390887042859558153L;
private Integer id;
private String username;
private String password;
}
UserDao.java
@Mapper
public interface UserMapper {
User selectByPrimaryKey(Integer id);
List<User> selectByUsername(@Param("username")String username);
}
<mapper namespace="com.hc.mapper.UserMapper">
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultType="User">
select
id, username, `password`
from db_test.tb_user
where id = #{id}
select>
<select id="selectByUsername" resultType="User">
select
id, username, `password`
from db_test.tb_user
where username=#{username}
select>
mapper>
public interface UserService {
User selectByPrimaryKey(Integer id);
List<User> selectByUsername(String username);
}
@Service
public class UserServiceImpl implements UserService{
@Resource
private UserMapper userMapper;
@Override
public User selectByPrimaryKey(Integer id) {
return userMapper.selectByPrimaryKey(id);
}
@Override
public List<User> selectByUsername(String username) {
return userMapper.selectByUsername(username);
}
}
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
//登录
@RequestMapping("/login")
public Result login(HttpServletResponse response, String username, String password) {
List<User> userList = userService.selectByUsername(username);
if (userList == null || userList.size() == 0) {
log.info("登录失败,用户不存在");
return Result.FAIL("登录失败,用户不存在");
}
String role = "admin";
for (User user : userList) {
if (user.getPassword().equals(password)) {
log.info("登录成功");
String token = JwtTokenUtil.createJWT(user.getId()+"",username,role);
JSONObject jsonObject = new JSONObject();
jsonObject.put("token",token);
return Result.SUCCESS(jsonObject);
}
}
log.info("登录失败,密码错误");
return Result.FAIL("登录失败,密码错误");
}
@LoginToken
@RequestMapping("/getUserById")
public User getUserById(HttpServletResponse response,Integer userId) {
log.info("访问需要登录才能访问的受保护的资源");
User user = userService.selectByPrimaryKey(userId);
return user;
}
// 不加注解的话默认不验证,登录接口一般是不验证的。
// getMessage()加上了登录注解,说明该接口必须登录获取token后,在请求头中加上token并通过验证才可以访问
@PassToken
@GetMapping("/getMsg")
public String getMsg() {
log.info("访问不需要登录就能访问的资源");
return "不需要通过验证";
}
}
创建项目,然后将jquery-2.2.4.min.js库拷贝到项目的根路径下的js目录中。项目结构如下图所示:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面title>
<script type="text/javascript" src="js/jquery-2.2.4.min.js">script>
head>
<body>
<form>
<input type="text" name="username" id="username" value="zhangsan"> <br>
<input type="text" name="password" id="password" value="123456"><br>
<input type="button" id="login" value="登录"/>
form>
<div id="show">div>
<script type="text/javascript">
$("#login").click(function () {
$.ajax({
url: "http://localhost:80/jd/user/login",
type: "POST",
data: {
username: $("#username").val(),
password: $("#password").val()
},
dataType: "json",
success: function (res) {
localStorage.setItem("token",res.data.token);
window.location.href="user.html";
},
error:function () {
$("#show").html("error");
}
});
});
script>
body>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户信息页面title>
<script type="text/javascript" src="js/jquery-2.2.4.min.js">script>
head>
<body>
<a href="http://localhost:80/jd/user/getMsg">请求公共信息a>
<div id="emp">div>
<script type="text/javascript">
$.ajax({
url: "http://localhost:80/jd/user/getUserById",
type: "POST",
data: {
userId: 1001,
token:localStorage.getItem("token")
},
dataType: "json",
success:function (res) {
$("#emp").text(JSON.stringify(res));
},
error:function () {
console.log("error");
}
});
script>
body>
html>