附:Token令牌和自定义注解
普通令牌写法(了解原理)
在一个完备的springboot项目中,可以通过request获得tonken
- 判断是否为空
- 判断redis中是否过期
/**
* @author : Lcywings
* @date : 2021/8/11 11:42
* @acl : true
* @description : 根据id查询订单详情
*/
@GetMapping("/orderInfo")
@ApiOperation(value = "查询订单详情", notes = "支持Token授权")
public RequestResult<String> queryOrderInfo(HttpServletRequest request, @RequestParam Integer id) {
// 校验权限,是否已经登录,只有已经登录用户才可以查询订单详情
String token = request.getHeader("token");
// 判断token是否为空
if (StringUtils.isEmpty(token)) {
return ResultBuildUtil.fail("9001", "token为空,查询失败");
}
// 判断token是否存在
Object userObject = redisUtils.get(token);
if (ObjectUtils.isEmpty(userObject)) {
return ResultBuildUtil.fail("9002", "token失效,查询失败");
}
//获取登录用户信息,根据用户编号,查询对应的订单信息
User user = JSON.parseObject(userObject.toString(), User.class);
log.info("------ 根据用户编号:{},订单编号:{},查询订单详情 ------", user.getUserId(), id);
// TODO 调用service层接口,到数据库查询订单详情
return ResultBuildUtil.success("查询订单详情成功!");
}
自定义注解实现:
/**
* @author : Lcywings
* @date : 2021/8/11 11:42
* @acl : true
* @description : 根据查询订单列表
*/
@GetMapping("/orderList")
@ApiOperation(value = "查询订单列表", notes = "支持Token授权")
@RequestPermission
public RequestResult<String> queryOrderList(javax.servlet.http.HttpServletRequest request) {
log.info("------ 根据用户编号:{},查询订单列表 ------", "U0001");
// TODO 调用service层接口,到数据库查询订单详情
return ResultBuildUtil.success("查询订单详情成功!");
}
实现自定义注解
编写自定义注解类
@RequestPermission--验证是否有令牌以及令牌是否过期
需要元注解支持,修改@Target,添加可以在类上使用
package com.lcywings.sbt.config.annotation;
import java.lang.annotation.*;
/**
* @author : Lcywings
* @date : 2021/8/11 11:57
* @acl : true
* @description : 自定义鉴权注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPermission {
}
为注解添加拦截功能
- 自定义一个拦截器实现HandlerInterceptor接口
- preHandle()方法,在调用控制层方法前调用---即为拦截
- checkRequestPermission()自定义方法,用来校验是否加了注解
package com.lcywings.sbt.intercept;
import com.lcywings.sbt.config.annotation.RequestPermission;
import com.lcywings.sbt.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
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;
/**
* Created on 2021/8/11.
* <p>
* Author : Lcywings
* <p>
* Description : 权限拦截器
*/
@Slf4j
public class SecurityInterceptor implements HandlerInterceptor {
@Autowired
private RedisUtils redisUtils;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
// 判断是否需要做权限校验(是否增加了自定义权限注解),如果需要,就要判断token是否有效,如果不需要,就只请求目标处理方法(放行)
if (this.checkRequestPermission(handler)) {
// 获取前端参数token,从redis中删除
String token = httpServletRequest.getHeader("token");
// 判断token是否为空
if (StringUtils.isEmpty(token)) {
log.warn("------ token为空,权限不足,请先登录再进行操作 ------");
// return false;
throw new RuntimeException("token为空,权限不足,请先登录再进行操作");
}
// 判断token是否存在
if (ObjectUtils.isEmpty(redisUtils.get(token))) {
log.warn("------ token为非法或者已失效,权限不足,请先登录再进行操作 ------");
// return false;
throw new RuntimeException("token为非法或者已失效,权限不足,请先登录再进行操作");
}
}
// 放行请求,或者权限校验通过,访问目标请求处理方法
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception e) throws Exception {
}
/**
* @author : Lcywings
* @date : 2021/8/11 14:39
* @acl : true
* @description : 判断目标方法是否增加了自定义权限校验注解
*/
private boolean checkRequestPermission(Object handler) {
// 判断当前handler对象是否映射到目标请求处理方法
if (handler instanceof HandlerMethod) {
// 强制转换为目标请求处理方法对象(封装了目标请求处理方法的所有内容)
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取目标请求处理方法的目标注解
RequestPermission requestPermission = handlerMethod.getMethod().getAnnotation(RequestPermission.class);
// 判断是否增加了权限校验自定义注解
if (null == requestPermission) {
// 没有加自定义权限校验注解,不代表不需要权限校验,还要看档期处理类上有没有加对应的权限校验注解
requestPermission = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RequestPermission.class);
}
// 判断是否获取到了自定义权限注解对象,如果获取到了,代表需要做权限校验,否则放行
return null != requestPermission;
}
// 不需要权限校验,直接放行
return false;
}
}
拦截器需要配置类
- 要将自定义拦截器注入容器
- 在addInterceptors()方法中添加拦截器
package com.lcywings.sbt.config.annotation;
import com.lcywings.sbt.intercept.SecurityInterceptor;
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;
/**
* Created on 2021/8/11.
* <p>
* Author : Lcywings
* <p>
* Description :
*/
@Configuration
public class PermissionConfig implements WebMvcConfigurer {
@Bean
public SecurityInterceptor securityInterceptor() {
return new SecurityInterceptor();
}
@Override
public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
}
@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
}
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
// 添加自定义拦截器
interceptorRegistry.addInterceptor(securityInterceptor()).addPathPatterns("/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {
}
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
}
@Override
public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {
}
@Override
public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public Validator getValidator() {
return null;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
自定义注解
@RequestUser--验证令牌中是否含有用户信息
控制层
/**
* @author : Lcywings
* @date : 2021/8/12 17:03
* @acl : true
* @description : 拉取用户信息
*/
@RequestAllow
@RequestMapping("/pullUserInfo")
public RequestResult<User> pullUserInfo(HttpServletRequest request, @RequestUser User u) {
return ResultBuildUtil.success(u);
}
注解类
package com.lcywings.sbt.config.annoation;
import java.lang.annotation.*;
/**
* @author : Lcywings
* @date : 2021/8/12 8:38
* @acl : true
* @description : 请求用户信息自定义注解,当参数增加此注解,就自动从redis中接续用户信息,封装到参数中
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestUser {
}
解析包下解析类
package com.lcywings.sbt.resolve;
import com.alibaba.fastjson.JSON;
import com.lcywings.sbt.beans.User;
import com.lcywings.sbt.config.annoation.RequestUser;
import com.lcywings.sbt.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.jws.soap.SOAPBinding;
/**
* Created on 2021/8/12.
* <p>
* Author : Lcywings
* <p>
* Description : 用户信息注解解析器
*/
@Slf4j
public class RequestUserResolver implements HandlerMethodArgumentResolver {
@Autowired
private RedisUtils redisUtils;
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 强制指定是否解析,方法的形参必须是增加了自定义用户信息注解:@RequestUser,且注解解析的目标实体类型就是此处指定的类型
return parameter.hasParameterAnnotation(RequestUser.class)
&& parameter.getParameterType().isAssignableFrom(User.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, @NotNull NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 执行参数解析,只有上面方法为true才会执行表
// 从redis中获取用户信息json字符串对象
// 校验权限,是否已经登录,只有已经登录用户才可以查询订单详情
// 此处无序做token有效性校验,一般需要解析用户讯息必须这么权限校验
Object userObj = redisUtils.get(webRequest.getHeader("token"));
log.info("redisUtils.get({}): ----- {}", webRequest.getHeader("token"), userObj);
// 返回目标实体类型
return JSON.parseObject(userObj.toString(), User.class);
}
}
在配置中注入bean
package com.lcywings.sbt.config;
import com.lcywings.sbt.config.annoation.RequestAllow;
import com.lcywings.sbt.config.annoation.RequestUser;
import com.lcywings.sbt.intercept.SecurityInterceptor;
import com.lcywings.sbt.resolve.RequestUserResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;package com.lcywings.sbt.resolve;
import com.alibaba.fastjson.JSON;
import com.lcywings.sbt.beans.User;
import com.lcywings.sbt.config.annoation.RequestUser;
import com.lcywings.sbt.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.jws.soap.SOAPBinding;
/**
* Created on 2021/8/12.
* <p>
* Author : Lcywings
* <p>
* Description : 用户信息注解解析器
*/
@Slf4j
public class RequestUserResolver implements HandlerMethodArgumentResolver {
@Autowired
private RedisUtils redisUtils;
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 强制指定是否解析,方法的形参必须是增加了自定义用户信息注解:@RequestUser,且注解解析的目标实体类型就是此处指定的类型
return parameter.hasParameterAnnotation(RequestUser.class)
&& parameter.getParameterType().isAssignableFrom(User.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, @NotNull NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 执行参数解析,只有上面方法为true才会执行表
// 从redis中获取用户信息json字符串对象
// 校验权限,是否已经登录,只有已经登录用户才可以查询订单详情
// 此处无序做token有效性校验,一般需要解析用户讯息必须这么权限校验
Object userObj = redisUtils.get(webRequest.getHeader("token"));
log.info("redisUtils.get({}): ----- {}", webRequest.getHeader("token"), userObj);
// 返回目标实体类型
return JSON.parseObject(userObj.toString(), User.class);
}
}
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;
/**
* Created on 2021/8/12.
* <p>
* Author : Lcywings
* <p>
* Description :
*/
@Configuration
public class PermissionConfig implements WebMvcConfigurer {
@Bean
public SecurityInterceptor securityInterceptor() {
return new SecurityInterceptor();
}
@Bean
public RequestUserResolver requestUserResolver() {
return new RequestUserResolver();
}
@Override
public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
}
@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
}
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
interceptorRegistry.addInterceptor(securityInterceptor()).addPathPatterns("/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {
}
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
}
@Override
public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {
}
@Override
public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolver) {
argumentResolver.add(requestUserResolver());
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public Validator getValidator() {
return null;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
课后作业:
将之前课程讲的微信授权登录+今天的token整合到一起(登录还支持授权登录,从微信拉取用户信息,进行登录)用户信息存储的token值:建议使用微信官方返回的access_token,它有实效,如果失效有其它限制,可以自定义超时时间出去面试:可以说token用的微信授权获取的access_token,有实效,自带刷新接口,如果是用户名密码登录,使用内部规则生成的,存入redis
Q.E.D.