1 大坑解决(security登录认证成功后,获取的用户一直是匿名用户)
【问题复现】
登陆成功后,调用其他接口,总是自动进入到我们的匿名用户未授权的过滤器,也就是前文写的这里
【问题分析】
security在UsernamePasswordAuthenticationFilter过滤器进行登录参数获取,但是之前还有一个过滤器
SecurityContextPersistenceFilter,看下源码
源码中这个过滤器会清除掉
SecurityContextHolder中的Context,导致我们SecurityContextHolder.getContext().getAuthentication();肯定会出问题
【问题解决】
思路如下:
既然SecurityContextPersistenceFilter过滤器在UsernamePasswordAuthenticationFilter之前就把
SecurityContextHolder中的Context清除了,那我们可以写一个过滤器,重新把SecurityContextHolder的context设置进去,并且此过滤器要放在UsernamePasswordAuthenticationFilter之后,那么就非常容易了
security中的过滤器一般都继承OncePerRequestFilter
package com.grm.security;
import com.alibaba.fastjson.JSON;
import com.grm.common.Result;
import com.grm.security.details.LoginUser;
import com.grm.util.JwtUtil;
import com.grm.util.RedisUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 解决天坑坑坑坑坑----------------
* 登录成功,一直401,显示匿名用户鉴权失败,是因为SecurityContextHolder自己把Context清楚了,我们需要重新设置一下
*
* @author gaorimao
* @date 2022/02/10
*/
@Slf4j
@Component
public class MyOncePerRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private RedisUtil redisUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(!"/login".equals(request.getRequestURI())){
log.info("[RequestURI] path = {}",request.getRequestURI());
String token = request.getHeader("Authorization");
if(ObjectUtils.isEmpty(token)){
Result.renderJsonStr(response,Result.failed(500,"获取token失败!"));
}
Claims claims = jwtUtil.parseToken(token);
String username = claims.getSubject();
if(ObjectUtils.isEmpty(username)){
Result.renderJsonStr(response,Result.failed(500,"token解析失败!"));
}
Object redisTokenObj = redisUtil.get("Authorization:"+username);
if(redisTokenObj == null){
Result.renderJsonStr(response,Result.failed(500,"token已过期!"));
}
Object loginUserObj = redisUtil.get("LoginUser:"+username);
if(loginUserObj == null){
Result.renderJsonStr(response,Result.failed(500,"获取当前登录用户失败!"));
}
LoginUser loginUser = (LoginUser)loginUserObj;
log.info("[LoginUser] redis loginUser = {}", JSON.toJSONString(loginUser));
/*
重新设置SecurityContextHolder
*/
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, loginUser.getPassword(), loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
}
把这个过滤器配置在SecurityConfig中
@Autowired
private MyOncePerRequestFilter myOncePerRequestFilter;
// 重新设置SecurityContextHolder
http.addFilterBefore(myOncePerRequestFilter, UsernamePasswordAuthenticationFilter.class);
至此大坑解决!
2 授权验证
其实至此,我们授权已经写好了,不过还是来验证下
首先我们再来捋一下
manager用户是有审批权限的(oa:approve:list)
user用户是没有审批权限的
那我们先用user账号登录,拿到user用户的token,再用postman工具发送请求OA审批列表的接口
user用户
manager用户
至此,基于security的权限控制到按钮,后台所有逻辑基本已经实现完成!
【登出时,又遇到大坑了】
前台调用/logout请求,一直显示跨域问题
之前把跨域配置类注入到security了
实际上不用写 http.addFilterBefor(crosFilter,XXX.class);这句话,删掉即可