1.前言
1.1.JWT说明
JWT全程JSON Web Token,是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名。
1.2.什么情况下需要用JWT
- Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
- Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Token无疑是一种很好的方式。因为JWT可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
1.3.JWT结构说明
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:
- Header:JWT头
- Payload:有效载荷
- Signature:签名
结构是这样的:xxxxxx.yyyyyy.zzzzzz
例如:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiMSIsImFkbWluIiwiMSJdLCJleHAiOjE2MjMwNzUyMzcsImlhdCI6MTYyMzA0NjQzN30.pT5NgZCkfN2WwQS8rp1Scifa07KbuvZbXO8y8596ACg
2.集成说明
2.1引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2.2.定义Token工具类
TokenUtil
package com.xxxxxx.sim.utils;
import com.auth0.jwt.JWT;
import com.xxxxxx.sim.model.AdminUser;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.math.BigInteger;
import java.util.List;
/**
* Token方法封装
* @author xxxx
* @date 20200428
*/
public class TokenUtil {
/**
* 获取Token里面的UserId
* @return
*/
public static String getTokenUserId() {
// 从 http 请求头中取出 token
String token = getRequest().getHeader("token");
String userId = JWT.decode(token).getAudience().get(0);
return userId;
}
/**
* 获取Token里面的UserName
* @return
*/
public static String getTokenUseName(){
// 从 http 请求头中取出 token
String token = getRequest().getHeader("token");
String userName = JWT.decode(token).getAudience().get(1);
return userName;
}
/**
* 获取request
*
* @return
*/
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
return requestAttributes == null ? null : requestAttributes.getRequest();
}
}
2.3.定义注解
@CheckToken需要验证Token注解
package com.xxxxxx.sim.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 需要验证Token
* @author xxxxxx
* @date 20200428
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckToken {
boolean required() default true;
}
@PassToken跳过Token验证注解
package com.xxxxxx.sim.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 跳过验证
* @author xxxxxx
* @date 20200428
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
需要验证Token注解
2.4.定义Token拦截器
AuthenticationInterceptor
package com.xxxxxx.sim.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.xxxxxx.sim.annotation.CheckToken;
import com.xxxxxx.sim.annotation.PassToken;
import com.xxxxxx.sim.model.AdminUser;
import com.xxxxxx.sim.service.AdminUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.math.BigInteger;
/**
* Token拦截器
* @author xxxxxx
* @date 20200428
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private AdminUserService adminUserService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("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(CheckToken.class)) {
CheckToken checkToken = method.getAnnotation(CheckToken.class);
if (checkToken.required()) {
// 执行认证
if (token == null) {
return false;
}
// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
return false;
}
AdminUser user = adminUserService.queryAdminUserById(new BigInteger(userId));
if (user == null) {
return false;
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassWord())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
return false;
}
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 {
}
}
2.5.初始化拦截器
package com.xxxxxx.sim.configuration;
import com.xxxxxx.sim.utils.AuthenticationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import java.util.List;
/**
* 新建Token拦截器
* @author xxxxxxx
* @date 20200429
*/
@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;
}
}
3.Token使用
3.1.定义token使用Service
package com.xxxxxx.sim.service;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.xxxxxx.sim.model.AdminUser;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* 获取Token Service
* @author xxxxxx
*/
@Service
public class TokenService {
/**
* 获取Token
* @param user
* @return
*/
public String getToken(AdminUser user) {
Date start = new Date();
//8小时有效时间
long currentTime = System.currentTimeMillis() + 8*60* 60 * 1000;
Date end = new Date(currentTime);
String token = "";
token = JWT.create().withAudience(user.getId().toString()).withIssuedAt(start).withExpiresAt(end)
.sign(Algorithm.HMAC256(user.getPassWord()));
return token;
}
}
3.2.登录Controller生成Token
package com.xxxxxx.sim.controller;
import com.xxxxxx.sim.base.Result;
import com.xxxxxx.sim.base.ResultEnum;
import com.xxxxxx.sim.base.ResultGenerator;
import com.xxxxxx.sim.model.AdminUser;
import com.xxxxxx.sim.service.AdminUserService;
import com.xxxxxx.sim.service.TokenService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
/**
* @author xxxxxx
*/
@Api(tags = "登录")
@RestController
public class LoginController {
@Autowired
private AdminUserService adminUserService;
@Autowired
private TokenService tokenService;
@ApiOperation(value = "登陆", notes = "登陆")
@ApiResponses({
@ApiResponse(code = 200,message = "成功",response = AdminUser.class),
})
@RequestMapping(value = "/login" ,method = RequestMethod.GET)
public Result<Object> login(@RequestParam String userName, @RequestParam String password, HttpServletResponse response){
AdminUser adminUser=adminUserService.queryAdminUserListByUserName(userName);
if(adminUser!=null)
{
if(adminUser.getPassWord().equals(password))
{
String token = tokenService.getToken(adminUser);
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
response.addCookie(cookie);
//将token返回前端
adminUser.setUserToken(token);
//将密码置空,不返回给前端
adminUser.setPassWord("");
return ResultGenerator.success(adminUser);
}else
{
return ResultGenerator.failure(ResultEnum.PASSWORD_ERROR);
}
}else
{
return ResultGenerator.failure(ResultEnum.USER_NOT_EXISTS);
}
}
}
3.3.接口需要验证Token
@CheckToken
@ApiOperation(value = "用户列表查询", notes = "用户列表查询")
@ApiImplicitParams({
@ApiImplicitParam(name = "key",value = "查询条件"),
@ApiImplicitParam(name = "pageNum",value = "当前页",required = true),
@ApiImplicitParam(name = "pageSize",value = "每页显示条数",required = true),
@ApiImplicitParam(name = "roleId",value = "角色id",required = true)
})
@ApiResponses({
@ApiResponse(code = 200,message = "成功",response = AdminUser.class),
})
@PostMapping(value = "queryAdminUserList")
public Result<Object> queryAdminUserList(String key, int pageNum, int pageSize,BigInteger roleId){
try {
//判空
if(roleId.longValue() <= 0){
return ResultGenerator.failure(ResultEnum.Param_Error) ;
}
PageResult pageUserList = adminUserService.queryAdminUserList(key,pageNum,pageSize,roleId);
return ResultGenerator.success("成功",pageUserList);
} catch (Exception e) {
e.printStackTrace();
return ResultGenerator.error("失败:" + e.getMessage());
}
}
在接口方法上加上@CheckToken注解即可
4.最后再Swagger2上增加token传入的说明
package com.xxxxxx.sim.configuration;
import com.xxxxxx.sim.base.ResultEnum;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.builders.*;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* <p>Swagger2配置类</p>
* @author xxxxxx
* @date 20200429
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* 创建API应用
* apiInfo() 增加API相关信息
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
* 本例采用指定扫描的包路径来定义指定要建立API的目录。
*
* @return
*/
@Bean
public Docket createRestApi() {
//添加全局响应状态码
List<ResponseMessage> responseMessageList = new ArrayList<>();
Arrays.stream(ResultEnum.values()).forEach(errorEnums -> {
responseMessageList.add(
new ResponseMessageBuilder().code(errorEnums.getCode()).message(errorEnums.getMessage()).responseModel(
new ModelRef(errorEnums.getMessage())).build()
);
});
ParameterBuilder aParameterBuilder = new ParameterBuilder();
aParameterBuilder.name("token").description("token验证信息").modelRef(new ModelRef("String")).parameterType("header").build();
List<Parameter> aParameters = new ArrayList<Parameter>();
aParameters.add(aParameterBuilder.build());
return new Docket(DocumentationType.SWAGGER_2)
// 添加全局响应状态码
.globalResponseMessage(RequestMethod.GET, responseMessageList)
.globalResponseMessage(RequestMethod.POST, responseMessageList)
.globalResponseMessage(RequestMethod.PUT, responseMessageList)
.globalResponseMessage(RequestMethod.DELETE, responseMessageList)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.jointcontrols.sim.controller"))
.paths(PathSelectors.any())
.build()
.useDefaultResponseMessages(false)
.globalOperationParameters(aParameters);
}
/**
* 创建该API的基本信息(这些基本信息会展现在文档页面中)
* 访问地址:http://项目实际地址/swagger-ui.html
*
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("SIM Manager APIs")
.description("SIM卡管理接口文档")
.termsOfServiceUrl("http://www.baidu.com")
.version("1.0.0")
.build();
}
}