spring boot+JWT实现前后端分离快速开发
直接步入正题
首先看pom.xml,主要是spring boot的依赖包以及jwt的依赖包。
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.steps.interest</groupId>
<artifactId>steps-background</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spring boot start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
<!-- spring boot end -->
<!-- mysql start -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.23</version>
</dependency>
<!-- mysql end -->
<!-- mybatis start -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- mybatis end -->
<!-- AOP start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- AOP end -->
<!-- lombok start -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- lombok end -->
<!-- commons-lang3 start -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<!-- commons-lang3 end -->
<!-- alibaba druid start -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.11</version>
</dependency>
<!-- alibaba druid end -->
<!-- java-jwt start -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!-- java-jwt end -->
<!-- javax.servlet-api start -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- javax.servlet-api end -->
<!-- fastjson start -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- fastjson end -->
<!-- cache start -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
<!-- cache end -->
</dependencies>
<build>
<finalName>api-interface</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.5.RELEASE</version>
<configuration>
<skip>true</skip>
</configuration>
<!--<configuration>
<mainClass>com.pcdd.open.OpenApplication</mainClass>
</configuration>-->
</plugin>
<!-- mybatis generator 自动生成代码插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
<overwrite>false</overwrite>
<verbose>false</verbose>
</configuration>
</plugin>
</plugins>
</build>
</project>
自定义注解PassToken与UserLoginToken
/**
* 用来跳过验证的PassToken
* @author hxin
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
/**
* 需要登录才能进行操作的注解UserLoginToken
* @author hxin
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
自定义异常枚举
/**
* 结果编码枚举
* @author hxin
*/
public enum ResultCodeEnum {
SUCCESS(1,"成功"),
FAILURE(-1,"失败"),
ISAUTH(-10000, "token校验不通过")
;
private Integer code;
private String message;
ResultCodeEnum(Integer code, String msg) {
this.code = code;
this.message = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
创建token拦截器的配置,InterceptorConfig
/***
* Token拦截器
* @author hxin
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**"); // 拦截所有请求
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> arg0) {
// TODO Auto-generated method stub
}
@Override
public void addCorsMappings(CorsRegistry arg0) {
// TODO Auto-generated method stub
}
@Override
public void addFormatters(FormatterRegistry arg0) {
// TODO Auto-generated method stub
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry arg0) {
// TODO Auto-generated method stub
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> arg0) {
// TODO Auto-generated method stub
}
@Override
public void addViewControllers(ViewControllerRegistry arg0) {
// TODO Auto-generated method stub
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer arg0) {
// TODO Auto-generated method stub
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer arg0) {
// TODO Auto-generated method stub
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer arg0) {
// TODO Auto-generated method stub
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {
// TODO Auto-generated method stub
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> arg0) {
// TODO Auto-generated method stub
}
@Override
public void configurePathMatch(PathMatchConfigurer arg0) {
// TODO Auto-generated method stub
}
@Override
public void configureViewResolvers(ViewResolverRegistry arg0) {
// TODO Auto-generated method stub
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {
// TODO Auto-generated method stub
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> arg0) {
// TODO Auto-generated method stub
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
// TODO Auto-generated method stub
return null;
}
@Override
public Validator getValidator() {
// TODO Auto-generated method stub
return null;
}
}
接着就是重中之重,JWT的拦截器,前端需要在head中入token,博主以"X-Token"命名
token的生成规则是用userId+loginTime,也就是用户id+用户登录时间
这里用到了cache缓存来存放用户信息
/**
* 拦截器
* @author hxin
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private AdminInfoDao adminInfoDao;
/**
* 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller
* 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
* @param httpServletRequest
* @param httpServletResponse
* @param object
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader("X-Token");// 从 http 请求头中取出 token
// 如果不是映射到方法直接通过
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()) {
return true;
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
// 执行认证
if (token == null) {
throw new TokenAuthorException("无token,请重新登录");
}
// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new TokenAuthorException("无效token,请重新登录");
}
Date loginTime = (Date) CacheUtil.get(userId);
boolean flag = false;
if (loginTime == null) {
AdminInfo adminInfo = adminInfoDao.queryById(Integer.valueOf(userId));
if (adminInfo == null) {
throw new TokenAuthorException("无效token,请重新登录");
}
loginTime = adminInfo.getLoginTime();
flag = true;
}
try {
StringBuilder str = new StringBuilder();
str.append(userId);
str.append(loginTime);
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(str.toString())).build();
jwtVerifier.verify(token);
if (flag) {
CacheUtil.put(userId, loginTime);
}
} catch (JWTVerificationException e) {
throw new TokenAuthorException("无效token,请重新登录");
}
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 {
}
}
生成token的方法
/**
* Server 用于下发Token
* @author hxin
*/
@Service("TokenService")
public class TokenService {
/**
* 生成token,无限时间
* @param user
* @return
*/
public String getToken(AdminInfo user) {
String token = "";
StringBuilder str = new StringBuilder();
str.append(user.getId());
str.append(user.getLoginTime());
return JWT.create().withAudience(String.valueOf(user.getId())).sign(Algorithm.HMAC256(str.toString()));
}
}
登录接口
/**
* 管理员登录
* @author hxin
*/
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@Autowired
private TokenService tokenService;
/**
* 登录接口
* @param account
* @param password
* @return
* @throws Exception
*/
@RequestMapping("/login")
public BaseResult<JSONObject> login(@RequestParam("account") String account, @RequestParam("password") String password) throws Exception {
//1.定义返回的json
JSONObject json = new JSONObject();
//2.校验参数必填
boolean isNull = new EmptyCheckUtils()
.addObject(account)//用户名
.addObject(password)//密码
.empty();
if (isNull) {
throw new ApiException("用户名和密码必填");
}
//3.业务
AdminInfo adminInfo = loginService.login(account, password);
if (null != adminInfo) {
//禁用
if (StatusCommonEnum.OFF.getStatus() == adminInfo.getStatus()) {
throw new ApiException("该管理员已被禁用");
}
//登录成功
String token = tokenService.getToken(adminInfo);
CacheUtil.put(adminInfo.getId() + "", adminInfo.getLoginTime());
json.put("admin", adminInfo);
json.put("token", token);
return new BaseResult<>(ResultCodeEnum.SUCCESS.getCode(), json, true, "");
} else {
throw new ApiException("账号或者密码错误");
}
}
}
使用postman测试登录接口,拿到token
在新增管理员的方法上加上自定义注解@UserLoginToken
/**
* 管理员
* @author hxin
*/
@RequestMapping("admin")
@RestController
public class AdminController {
@Autowired
private AdminService adminService;
@Autowired
private HttpServletRequest request;
/**
* 添加管理员
* @param
* @return
* @throws SQLException
*/
@UserLoginToken
@RequestMapping("insert")
public BaseResult<?> insertAdmin(@RequestParam("account") String account, @RequestParam("password") String password,
@RequestParam("roleId") String roleId) throws Exception {
String token = request.getHeader("X-Token");
//当前用户id作为parentId
String parentId = TokenUtils.getTokenUserId(token);
return new BaseResult(ResultCodeEnum.SUCCESS.getCode(), adminService.insert(account, password, roleId, parentId), true, "");
}
}
使用postman测试新增管理员接口,这里测试时未往headers加入token(token错了也是一样)
使用正确的token则是这样的
如此,JWT的用法就是这样,欢迎补充~
【原创】未经许可,禁止转载