前言
最近在做权限管理,需要获取用户所属的角色信息,但是这些信息通过前端传参很不安全。大家可以试想这么一个场景:有两个用户A和B,A的角色是1,B的角色是2;我登录的是A用户,如果通过前端手动传入角色id获取信息(/getValue/1),我就可以绕过前端,手动向后端发送:/getValue/2,获取不属于我角色能获取到的信息,这样就会出现安全问题。那么这样的问题有解吗?肯定是有的!如果你还是偏爱前端传递,那么只需要把前端传递的roleId和登录角色的roleId进行比较,但是这样操作起来就很麻烦。
获取用户登录信息
如果我们不用前端传递参数呢?那就可以从请求头中获取用户的登录信息!每一个登录后的请求都会携带token或cookie
因此我们就可以通过请求头获取用户的登录信息
public R responsibleNameList(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
String userByToken = userTokenService.getUserByToken(authorization);
User user = userService.getById(Long.valueOf(userByToken));
......
}
其中,getUserByToken方法要看你们的项目在用户登录拦截时,如何保存的用户信息,以我的项目为例,我们保存在了redis中
......
// 登录成功发放token令牌
String token = userTokenService.createToken(userInfo.getName(), String.valueOf(userInfo.getId()));
......
@Service
public class UserTokenServiceImpl implements UserTokenService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SecurityProperties securityProperties;
@Autowired
private TokenProvider tokenProvider;
@Override
public void refreshToken(String token) {
redisTemplate.expire(token, securityProperties.getRenew(), TimeUnit.MILLISECONDS);
}
@Override
public String getToken(String userId) {
return (String) redisTemplate.opsForValue().get(securityProperties.getUserLoginKey() + userId);
}
@Override
public String createToken(String username, String userId) {
String token = tokenProvider.createToken(username);
//token保存到redis
this.saveUser(token, userId, securityProperties.getTokenValidityInSeconds());
return token;
}
@Override
public void saveUser(String token, String userId, Long time) {
if (!securityProperties.isMultiLogin()) {
Object oldTokenKey = redisTemplate.opsForValue().get(securityProperties.getUserLoginKey() + userId);
if (oldTokenKey != null) {
redisTemplate.delete(oldTokenKey);
}
}
redisTemplate.opsForValue().set(token, userId, time, TimeUnit.SECONDS);
redisTemplate.opsForValue().set(securityProperties.getUserLoginKey() + userId, token);
}
@Override
public String getUserByToken(String token) {
return (String) redisTemplate.opsForValue().get(token);
}
}
通过该方式,就可以获取到用户的登录信息。
但是这样的话,我每个需要用户登录信息的请求,都要执行一遍request.getHeander吗?答案是yes,是的,不用我说,你也会觉得这样的方式很麻烦,对吧?
获取用户信息进阶版
在我们学习Spring时,我们知道,所有的请求都会经过拦截器的拦截处理,然后放行;我们又知道,注解可以简化一些通用的操作,比如@Data就简化了对象的get和set方法。那么我们是不是可以将以上两者结合起来,简化我们的开发?答案一定是yes!
首先,我们定义了一个自定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}
然后编写一个登录处理器
@Component
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Resource
private UserService userService;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
NativeWebRequest request, WebDataBinderFactory factory) {
String authorization = request.getHeader("Authorization");
String userByToken = userTokenService.getUserByToken(authorization);
User user = userService.getById(Long.valueOf(userByToken));
return user;
}
}
最后在Spring中配置该处理器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
}
......
}
这样就完成了!只需要在请求中加入我们的注解
public R responsibleNameList(@CurrentUser User user) {
// 直接进行后续的操作
......
}
总结
使用自定义注解可以极大的简化我们的代码开发,但是也要结合实际的业务需求,不能为了注解而注解。谢谢大家的观看,我们下次再见。