最近学习时,自己项目中用到了Token,涉及到User和Admin两个角色,普通的用户并没有很大的权限,和Admin拥有较大的权限。每次在写代码前都需要重复校验角色,根据角色来决定是否有操作这个接口的权限。
于是我想到,能不能定义一个注解,在注解的参数中输入这个接口可访问的角色,同时再定义一个接口,得到对应token的参数。
项目没有涉及到RBAC模式,就划分Admin和User,因此思路还是比较好理解的。
1.定义一个Token注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,实现Token的校验
* 2021-9-23 09:39:37
* @author weilinlin
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Token {
public static final String USER_KEY = "USER_ID";
//开关,默认打开,设为false时关闭
boolean validate() default true;
//角色信息,支持多个角色
String roles() ;
}
2.设定第二个注解,用于在解析Token时,返回对应的数据,如Id、role
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,获取用户的信息
* 2021-9-23 09:39:37
* @author weilinlin
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
3.编写解析逻辑:,需要将解析后的数据放到request中
import entity.BaseController;
import entity.StatusCode;
import entity.TokenEntity;
import exception.CourseException;
import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import utils.ComStringUtil;
import utils.JwtUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(BaseController.class);
public static final String USER_KEY = "SMOYU";
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Token annotation;
if(handler instanceof HandlerMethod) {
annotation = ((HandlerMethod) handler).getMethodAnnotation(Token.class);
}else{
return true;
}
//没有声明需要权限,或者声明不验证权限
if(annotation == null || annotation.validate() == false){
return true;
}
//没有传送角色信息,默认为不校验权限
if(ComStringUtil.isEmpty(annotation.roles())){
return true;
}
//从header中获取token
String header = request.getHeader("Authorization");
if(ComStringUtil.isEmpty(header)){
throw new CourseException(StatusCode.ACCESSERROR,"权限不足!");
}
logger.info("请求头:"+header);
//得到token字符串
String token = ComStringUtil.getTokenBySubs(header);
if(ComStringUtil.isEmpty(token)){
throw new CourseException(StatusCode.ACCESSERROR,"权限不足!");
}
//解析token
Claims claims = null;
try {
claims = jwtUtil.parseJWT(token);
}catch (Exception e){
throw new CourseException(StatusCode.ACCESSERROR,"Token校验失败!请重新登录");
}
if(claims == null){
throw new CourseException(StatusCode.ACCESSERROR,"Token校验失败!请重新登录");
}
logger.info("Token解析数据:"+claims);
//得到角色信息,只要有一个角色符合,就能返回
String[] roles = annotation.roles().split(",");
logger.info("角色参数:"+roles);
TokenEntity entity = new TokenEntity();
for (String role : roles) {
if(!role.equals(claims.get("roles"))){
continue;
}
entity.setId(Long.valueOf(claims.getId()));
entity.setRoles((String) claims.get("roles"));
request.setAttribute(USER_KEY,entity);
return true;
}
throw new CourseException(StatusCode.ACCESSERROR,"权限不足!请使用正确的账号操作。");
}
}
4.从request中拿数据
import com.smoyu.user.annotation.LoginUser;
import entity.TokenEntity;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.util.Map;
@Component
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver
{
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType().isAssignableFrom(TokenEntity.class)&&methodParameter.hasParameterAnnotation(LoginUser.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
//获取登陆用户信息
Object object = nativeWebRequest.getAttribute(AuthorizationInterceptor.USER_KEY, RequestAttributes.SCOPE_REQUEST);
if(object == null){
return null;
}
return (TokenEntity)object;
}
}
5.拦截对应的路径即可:
@Configuration
public class JwtConfig extends WebMvcConfigurationSupport {
@Autowired
private AuthorizationInterceptor jwtInterceptor;
@Autowired
private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;
// private String fileSavePath = "/usr/xiaomoyu/images/";
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/**/login/**");
super.addInterceptors(registry);
}
/**
* 发现如果继承了WebMvcConfigurationSupport,则在yml中配置的相关内容会失效。 需要重新指定静态资源
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations(
"classpath:/static/");
registry.addResourceHandler("swagger-ui.html").addResourceLocations(
"classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars/");
//设定本地文件访问路径
/* registry.addResourceHandler("/images/**")
.addResourceLocations("file:"+fileSavePath);*/
super.addResourceHandlers(registry);
}
/**
* 实现LoginUser注解,从请求request中得到参数
* @param argumentResolvers
*/
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
}
}
使用方式:
@GetMapping("/userInfo/token")
@Token(roles = "student")
public Result getUserInfo(@LoginUser TokenEntity tokenEntity){
}
加入Token,roles如果有多个角色就用逗号隔开。
在方法参数上加入注解@LoginUser即可。
大功告成