上一篇写了认证,授权其实就是把数据库查出来的角色、权限交给security来管理对比
首先从数据库中查找到用户所拥有的角色信息放入UserDetailsService的实现类里
User user = userRepository.selectByUserName(username);
//封装权限信息 这里从数据库通过userId查询出来的权限集合
List<SysRole> roles = userRepository.getRoleByUserId(user.getUserId());
List<String> collect = roles.stream().map((role) -> {
return role.getRoleName();
}).collect(Collectors.toList());
for (String s : collect) {
log.info("用户角色名:{}",s);
}
LoginUser loginUser = new LoginUser();
loginUser.setUser(user);
List<SimpleGrantedAuthority> authorities =
collect.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
loginUser.setAuthorities(authorities);
然后需要准备查询所有的请求路径下对应的角色
例如请求/user/** 的访问角色有ROLE_admin和ROLE_stu
/admin/** 的访问角色有ROLE_admin
@Service
public class MenuService {
@Autowired
private UserRepository userRepository;
public List<SysMenu> getMenus(){
return userRepository.getMenus();
}
}
<resultMap id="menus_map" type="com.lxf.mytest01.pojo.entity.SysMenu">
<id column="id" property="id"/>
<result column="menu_id" property="menuId"/>
<result column="menu_url" property="menuUrl"/>
<collection property="roles" ofType="com.lxf.mytest01.pojo.entity.SysRole">
<id column="rid" property="id"/>
<result column="roleid" property="roleId"/>
<result column="rolename" property="roleName"/>
</collection>
</resultMap>
<select id="getMenus" resultMap="menus_map">
select m.*,r.id as rid,r.role_id as roleid,r.role_name as rolename
from sys_role_menu rm LEFT JOIN sys_menu m ON m.menu_id =rm.menu_id
LEFT JOIN sys_role r on rm.role_id = r.role_id
</select>
之后创建一个filter 交给它来过滤请求用户是否拥有所对应的角色
@Slf4j
@Component
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
private MenuService menuService;
/**
* 路径匹配器
*/
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
/**
* 匹配路径,返回角色列表 (如果这个请求url含有对应角色才能访问,返回对应角色)
* @param object
* @return
* @throws IllegalArgumentException
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//获取请求路径
String requestUrl = ((FilterInvocation) object).getRequestUrl();
log.info("requestUrl : {}",requestUrl);
//查询数据库 获取所有菜单
List<SysMenu> menus = menuService.getMenus();
for (SysMenu menu : menus) {
//如果路径匹配
if(antPathMatcher.match(menu.getMenuUrl(),requestUrl)){
//获取对应角色
List<SysRole> roles = menu.getRoles();
String[] roleNames = new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
roleNames[i] =roles.get(i).getRoleName();
}
log.info("对应路径查询到的角色:{}", Arrays.toString(roleNames));
return SecurityConfig.createList(roleNames);
}
}
//设置默认访问角色 游客模式
return SecurityConfig.createList(SysRoleCode.ROLE_GUEST.roleName());
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
这个fitler返回一个或者多个能访问这个路径的角色交给下一个来鉴权
@Slf4j
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
*
* @param authentication 用户信息(UserDetailsService 获取到)
* @param object
* @param configAttributes 请求地址对应的角色信息(数据库查出的数据)
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
log.info("进入权限鉴定~");
for (ConfigAttribute configAttribute : configAttributes) {
//判断是否游客模式
if(StringUtils.equals(SysRoleCode.ROLE_GUEST.roleName(),configAttribute.getAttribute())){
//匿名用户
if(configAttribute instanceof AnonymousAuthenticationToken){
log.error("匿名用户访问");
throw new AccessDeniedException("无访问权限");
}else {
//已经登录的用户,不需要权限的访问地址 直接放行
return;
}
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
log.info("用户所拥有的角色 ------------------> : {}",authority.getAuthority());
log.info("路径所需要的角色 ------------------> : {}",configAttribute.getAttribute());
if(StringUtils.equals(authority.getAuthority(),configAttribute.getAttribute())){
//如果用户角色和路径所需角色相同,则放行
return;
}
}
}
throw new AccessDeniedException("无访问权限");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
在之前为了不每次都操作数据库,就在自己的loginService里把查询到的用户角色放入redis中
List<SysRole> roles = userRepository.getRoleByUserId(userId);
List<String> rolesNames = roles.stream().map((role) -> {
return role.getRoleName();
}).collect(Collectors.toList());
redisUtil.del("authorities"+userId);
String jsonString = JSONObject.toJSONString(rolesNames);
redisUtil.set("authorities"+userId,jsonString,60*60);
然后需要在jwtFilter里加入角色权限,每次请求都会查询权限信息
//获取用户权限角色信息
String o = (String) redisUtil.get("authorities" + userId);
List<String> range = JSONObject.parseArray(o, String.class);
log.info("redis role list size: {}",range.size());
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
//如果redis中有,就从redis中获取
if(Objects.nonNull(range)&&range.size()>0){
authorities =
range.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}else {
// 如果Redis中没有,就在数据库中查询,并放入redis
List<SysRole> roles = userRepository.getRoleByUserId(userId);
List<String> rolesNames = roles.stream().map((role) -> {
return role.getRoleName();
}).collect(Collectors.toList());
authorities =
rolesNames.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
redisUtil.set
("authorities"+userId,JSONObject.toJSONString(rolesNames),3600l);
}
//存入securityContextHolder
//封装到这个类中 获取权限信息user.getAuthorities()
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(user,null,authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request,response);
最后就是在securityConfig中把上面的注入进去
@Autowired
private MyAccessDecisionManager myAccessDecisionManager;
@Autowired
private MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
http.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(myAccessDecisionManager);
object.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
return object;
}
})