赞
踩
@RestController
public class HelloController {
@RequestMapping(value = "/user",method = RequestMethod.POST)
@PreAuthorize("hasRole('admin')")
public String user() {
return "admin";
}
}
当需求是需要动态管理API接口,比如A用户 只开放ABCD四个接口,B用户只开放EFGH四个接口。
或者是当用户A刚注册只有A接口功能,用户A开通会员就有B接口功能。
1、自定义一个接口权限注解,参数包括接口地址、服务ID 。方便管理接口是哪个服务的
2、服务一启动时,自动搜集所有使用这个注解的接口,然后保存到数据库。这样权限的字典表就有了
3、管理员给用户配置接口权限。如用户A 配置A、B、C、D四个接口的权限
/** * @author czx * @title: PermissionAuth * @projectName zhjg * @description: TODO 接口权限验证注解 * @date 2020/5/1811:35 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResourceAuthPer { // 接口中文注释 String value() default ""; // 服务 id String[] exampleId() default {}; }
@RestController
@RequestMapping("/visitor")
public class VisitorController {
@ApiOperation(value = "来访人员列表")
@RequestMapping(value = "/visited",method = RequestMethod.POST)
@ResourceAuthPer(value = "来访人员列表",exampleId = "visitor-service")
public R visited(@RequestBody Visitor params){
List<Visitor> data = VisitorService.list();
return R.ok().setData(data);
}
}
/** * @author czx * @title: PermissionConfig * @projectName zhjg * @description: TODO 收集所有URL ROLE * @date 2020/5/1811:35 */ @Slf4j @Component public class PermissionConfig implements InitializingBean { @Value("${spring.application.name}") private String applicationName; @Autowired private WebApplicationContext applicationContext; @Getter @Setter private List<PermissionEntityVO> permissionEntities = Lists.newArrayList(); @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } @SneakyThrows @Override public void afterPropertiesSet(){ RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods(); map.keySet().forEach(mappingInfo -> { HandlerMethod handlerMethod = map.get(mappingInfo); ResourceAuthPer method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), ResourceAuthPer.class); Optional.ofNullable(method) .ifPresent(resourcePermission -> mappingInfo .getPatternsCondition() .getPatterns() .forEach(url -> { String strUrl = URLConvertUtil.capture(url); String permission = URLConvertUtil.convert(url); if(ArrayUtil.isNotEmpty(method.exampleId())){ for (String exampleId : method.exampleId()){ permissionEntities.add(PermissionEntityVO .builder() .sName(method.value()) .sApplyExampleID(Arrays.asList(exampleId)) .sEnglishName(permission + "_" + exampleId) .sServiceName(applicationName) .sUrl(strUrl) .build()); } } })); }); } }
/** * @author czx * @title: NacosStartListener * @projectName zhjg * @description: TODO * @date 2020/12/2217:52 */ @Slf4j @Component public class NacosStartListener implements ApplicationListener<ApplicationReadyEvent> { @Autowired private GatewayControllerEndpoint endpoint; @Value("${spring.cloud.nacos.discovery.server-addr}") private String nacosAddress; @Autowired private SyncPermissionController controller; @Autowired private ServiceNameData serviceNameData; public HashMap<String,Long> serviceStartTime = new HashMap<>(); // 排除seata服务 public String SAETASERVER = "seata-server"; @SneakyThrows @Override public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { NamingService namingService = NacosFactory.createNamingService(nacosAddress); endpoint.routes().subscribe(stringObjectMap -> { String uri = (String) stringObjectMap.get("uri"); String service = uri.substring(5); if(!service.equals(SAETASERVER)){ try { log.info("开始监听服务[{}]...",service); namingService.subscribe(service, (event) ->{ if (event instanceof NamingEvent){ NamingEvent namingEvent = (NamingEvent) event; List<Instance> list = namingEvent.getInstances(); String serviceName = namingEvent.getServiceName().substring(15); if(list != null && list.size() != 0){ Long time = null; if(serviceStartTime != null && serviceStartTime.get(serviceName) != null){ time = MapUtil.getLong(serviceStartTime, serviceName); } if(time == null || System.currentTimeMillis() - time > 10000 ){ log.info("服务[{}]状态:上线",serviceName); controller.syncService(serviceName); serviceStartTime.put(serviceName,System.currentTimeMillis()); serviceNameData.setServiceList(serviceStartTime); } } } }); } catch (NacosException e) { e.printStackTrace(); } } }); } }
@Slf4j @RestController @RequestMapping("/sync-permission") public class SyncPermissionController { @Value("${spring.application.name}") private String applicationName; @Value("${sync.permission:false}") public boolean permission; @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient client; public ConcurrentHashMap<String,Integer> status = new ConcurrentHashMap(); @RequestMapping(value = "/syncService") public void syncService(String serviceId){ if(permission){ this.sync(serviceId); }else { log.info("服务[{}]自动同步权限未打开!",serviceId); } } private synchronized String sync(String serviceId){ int temp = 1; if(!serviceId.equals(applicationName)){ String url = "http://" + serviceId; try{ log.info("服务[{}]正在同步权限...",serviceId); if(status.size() > 0 && status.get(serviceId) != null){ temp = status.get(serviceId); temp ++; } // 调用服务保存到数据库,如果存在就更新,不存在就保存 ResponseEntity<String> body = restTemplate.getForEntity(url + "/security/permission/sync", String.class); if(body.getStatusCodeValue() == 200){ status.put(serviceId,1); log.info("服务[{}]权限同步成功!",serviceId); return "权限同步成功"; }else { log.error("服务[{}]权限同步错误:{}",serviceId,body.getBody()); return "权限同步错误"; } }catch (Exception e){ log.error("服务[{}]权限同步失败,可能没有启动!",serviceId); if(temp > 10){ status.put(serviceId,1); return "权限同步失败"; }else { try { status.put(serviceId,temp); log.error("服务[{}]休息{}s后,第{}次重试.....",serviceId,temp,temp); Thread.sleep(temp * 1000); this.sync(serviceId); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } return "权限同步错误"; } }
到此,接口权限就保存到数据库中了。
@Slf4j @Component public class RemoteUserDetailsService implements UserDetailsService { @Autowired private RemoteAdminService remoteAdminService; @Autowired private PermissionRemoteService permissionRemoteService; @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { BdUserDto bdUserDto = remoteAdminService.selectUserByUserName(userName); // 校验 if (ObjectUtil.isNull(bdUserDto) || StrUtil.isEmpty(bdUserDto.getSUserLoginID())) { // 用户不存在 log.info("登录用户:{} 不存在.", userName); throw new UsernameNotFoundException("登录用户:" + userName + " 不存在"); } return getDetail(bdUserDto); } private UserDetails getDetail(BdUserDto bdUserDto){ Set<String> permissions = new HashSet<>(); R result = permissionRemoteService.getPermissionByUserId(bdUserDto.getSUserLoginID()); ArrayList data = MapUtil.get(result, "data", ArrayList.class); if(CollUtil.isNotEmpty(data)){ permissions.addAll(data); } String[] roles = new String[0]; if(CollUtil.isNotEmpty(permissions)){ roles = permissions.stream().map(role -> "ROLE_" + role).toArray(String[]::new); } Collection<? extends GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(roles); CustomUserDetailsUser customUserDetailsUser = new CustomUserDetailsUser(bdUserDto,bdUserDto.getSUserName(),bdUserDto.getSPassword(),authorities); return customUserDetailsUser; } }
给用户配置权限的CURD代码就不贴了。
@Slf4j @Configuration @EnableWebSecurity public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Autowired private AuthIgnoreConfig authIgnoreConfig; @SneakyThrows @Override protected void configure(HttpSecurity http) { List<String> permitAll = authIgnoreConfig.getIgnoreUrls(); String[] urls = permitAll.stream().distinct().toArray(String[]::new); ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests(); registry .antMatchers(urls) .permitAll() .anyRequest() .authenticated() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O o) { //设置自定义访问决策管理器 o.setAccessDecisionManager(accessDecisionManager()); //设置自定义的权限数据源 o.setSecurityMetadataSource(filterInvocationSecurityMetadataSource()); return o; } }) .and().csrf().disable(); } @Bean public AccessDecisionManager accessDecisionManager(){ return new CustomAccessDecisionManager(); } @Bean public FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource(){ return new CustomFilterInvocationSecurityMetadataSource(); } }
public class CustomAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException{ // 如果这个url 没有配置权限 直接放行 if(CollUtil.isEmpty(collection)) { return; } ConfigAttribute c; String needRole; for(Iterator<ConfigAttribute> iter = collection.iterator(); iter.hasNext(); ) { c = iter.next(); needRole = c.getAttribute(); for(GrantedAuthority ga : authentication.getAuthorities()) { if(needRole.trim().equals(ga.getAuthority())) { return; } } } throw new AccessDeniedException("没有权限访问"); } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } }
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private final AntPathMatcher antPathMatcher = new AntPathMatcher(); private final String ROLE = "ROLE_"; @Autowired private PermissionConfig permissionConfig; @Autowired private AuthIgnoreConfig authIgnoreConfig; @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { FilterInvocation fi = (FilterInvocation) object; String url = fi.getRequestUrl(); List<String> ignoreUrls = authIgnoreConfig.getIgnoreUrls(); if(CollUtil.isNotEmpty(ignoreUrls) && ignoreUrls.contains(url)){ // 如果是忽略认证的直接放行 return null; } List<PermissionEntityVO> permissionEntities = permissionConfig.getPermissionEntities(); for (PermissionEntityVO vo : permissionEntities){ if(antPathMatcher.match(vo.getSUrl(),url)){ return SecurityConfig.createList(ROLE + vo.getSEnglishName()); } } //如果这个url 没有配置权限 直接返回空 return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> aClass) { return false; } }
到此就实现了用户接口权限验证。
1、服务启动 -> 收集URL权限 -> 保存或更新URL权限
2、管理员给用户配置已保存的权限
3、用户登录成功 -> 读取所配置的权限 -> 设置到security的user中
4、用户访问接口 -> security框架会在访问决策管理器AccessDecisionManager验证当前user中有没有对应的url权限
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。