01
—
jwt简介
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
作为session的替代品,可以很好的应用到前后端分离的项目当中
02
—
思路
接口设计采用restful风格规范,前后端交互采用json
大体思路为:
用户使用用户名密码或者其他方式验证方式请求服务器
服务器进行验证用户的信息
服务器通过验证发送给用户一个token
客户端存储token,并在每次请求时附送上这个token值
服务端验证token值,并返回数据
03
—
写写代码
将主要代码进行了整理,稍微有点代码基础的应该都能很好的理解
如果我哪里没写清楚,没看明白的,也可以私信我进行咨询
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
拦截/mrc下的所有请求,但是对登录不做拦截
@Configuration
public class APIIntercepterConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new APIInterceptor())
.addPathPatterns("/mrc/**")
.excludePathPatterns("/mrc/login/**");
super.addInterceptors(registry);
}
}
对于拦截的前端请求,从header拿到token
先判断请求的接口方法有没有PassToken注解(后边会讲到,如果方法上配置了这个注解,则直接放行)
然后判断是否有UserLoginToken注解,如果有,则进行token验证
获取到用户信息,如果用户不存在或者token验证没通过,则返回401异常
afterCompletion方法中的一些配置是为了解决跨域存在的一些问题
@Component
public class APIInterceptor implements HandlerInterceptor {
@Autowired
IUserService userService;
@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(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
User user = UserUtils.get(userId);
if (user == null) {
throw new RuntimeException("用户不存在,请重新登录");
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getLoginName())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// System.out.println("方法处理中==========");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "Authorization,Content-Type,X-Requested-With,token");
response.setHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT,DELETE");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Max-Age", "3600");
// System.out.println("方法处理后==========");
}
我这里是用用户的loginName做生成token的密钥和签名
配置token的有效时长
@Service
public class TokenService {
public TokenInfo getToken(User user) {
Date start = new Date();
//一小时有效时间
long expiresIn = 60 * 60 * 1000;
long currentTime = System.currentTimeMillis() + expiresIn;
Date end = new Date(currentTime);
String token;
//以用户的云信id做唯一标识
token = JWT.create().withAudience(user.getLoginName()).withIssuedAt(start).withExpiresAt(end)
.sign(Algorithm.HMAC256(user.getLoginName()));
TokenInfo tokenInfo = new TokenInfo();
tokenInfo.setLoginName(user.getLoginName());
tokenInfo.setToken(token);
tokenInfo.setExpiresIn(expiresIn);
tokenInfo.setTokenUpdateTime(start.getTime());
return tokenInfo;
}
}
作用在接口方法上
/***
* 用来跳过验证的 PassToken
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
作用在接口方法上
/**
* 用于登录后才能操作的token
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
这里是做的集成到云信(企业微信)的单点登录
在云信平台中,通过点击应用,会重定向到login接口,并传递两个参数code和state
其中code用户从云信(企业微信)获取用户信息
state为自定义的一个字符串,用作后端程序对重定向来源的一个验证
验证没有问题,则生成token,并重定向到前端的地址,并附带用户信息和token
getToken接口用作token的更新
给testToken加上@UserLoginToken注解,用作验证是否生效
@Api("用户登录管理")
@Controller
@RequestMapping("/mrc")
public class LoginAPI {
/**
* 前端首页地址
*/
public final String MRC_INDEX_URL = "";
public final String MRC_ERROR_URL = "/error";
@Autowired
IUserService userService;
@Autowired
TokenService tokenService;
/**
* 单点登录
*/
@RequestMapping("/login")
public void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
String code = request.getParameter("code");
String state = request.getParameter("state");
System.out.println(code + "===" + state);
if (!YunChatTokenUtils.STATE.equalsIgnoreCase(state) || StringUtils.isEmpty(code)) {
response.sendRedirect(MRConstant.MARKETING_RESOURCES_CLIENT_URL_HEADER + MRC_ERROR_URL);
}
//获取用户的基本信息
JSONObject userJSONObject = OAuth2Utils.getUserInfoByCode(code);
// 获取用户信息成功
if (userJSONObject != null
&& YunChatConstant.ERROR_CODE_SUCCESS == (userJSONObject.getIntValue(OAuth2Utils.KEY_ERRCODE))) {
User user = OAuth2Utils.getUserByUserTicket(userJSONObject.getString(OAuth2Utils.KEY_USER_TICKET));
User mrUser = UserUtils.get(user.getLoginName());
if (mrUser == null) {
userService.insertUser(user);
mrUser = userService.selectUserByLoginName(user.getLoginName());
UserUtils.addOrUpdate(mrUser);
}
TokenInfo tokenInfo = tokenService.getToken(mrUser);
response.sendRedirect(MRConstant.MARKETING_RESOURCES_CLIENT_URL_HEADER + MRC_INDEX_URL
+ "?token=" + URLEncoder.encode(JSONObject.toJSONString(tokenInfo)));
} else {
response.sendRedirect(MRC_ERROR_URL);
}
}
/**
* test
* 更新token
*/
@ApiOperation("获取更新token")
@ApiImplicitParam(name = "loginName", value = "用户登录名", required = true, dataType = "String", paramType = "path")
@UserLoginToken
@ResponseBody
@RequestMapping(value = "/token/{loginName}", method = RequestMethod.GET)
public AjaxResult getToken(@PathVariable("loginName") String loginName) {
User user = UserUtils.get(loginName);
TokenInfo tokenInfo = tokenService.getToken(user);
if (tokenInfo == null) {
return AjaxResult.error("更新token失败");
}
MRTokenVO mrTokenVO = new MRTokenVO();
mrTokenVO.setExpiresIn(tokenInfo.getExpiresIn());
mrTokenVO.setToken(tokenInfo.getToken());
mrTokenVO.setTokenUpdateTime(tokenInfo.getTokenUpdateTime());
MRUserVO mrUserVO = new MRUserVO(user);
mrTokenVO.setUser(mrUserVO);
return AjaxResult.apiSuccess(mrTokenVO);
}
/**
* test
* 更新token
*/
@ApiOperation("测试token")
@UserLoginToken
@ResponseBody
@RequestMapping(value = "/token/test/{loginName}", method = RequestMethod.GET)
public AjaxResult testToken(@PathVariable("loginName") String loginName) {
return AjaxResult.apiSuccess("测试成功");
}
}
文/戴先生@2020年5月30日
---end---