【MS】Spring Security Oauth接口访问权限,一个注解就可以自动保存到数据库

权限框架
  • spring-security-oauth2
常规系统

都像这样写,然后在数据库写添加这个xxxxxx 权限,在配置角色时把这个xxxxxx 权限给配置对应角色,当用户登录后,访问这个接口,就会验证用户是否有这个权限
在这里插入图片描述

实现自动配置
1、创建自定义注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResourcePermission {

    String value() default "";

}
2、在项目启动时加载所有带权限注解的接口,然后保存到数据库
@Slf4j
public class ResourcePermissionConfig implements InitializingBean {

    @Autowired
    private WebApplicationContext applicationContext;

    @Value("${spring.application.name}")
    private String applicationName;

    @Autowired
    private PermissionProducer permissionProducer;

    @Getter
    @Setter
    private List<PermissionEntityVO> permissionEntities = Lists.newArrayList();

    @Override
    public void afterPropertiesSet(){
        log.info("===============ResourcePermissionConfig==============");
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        map.keySet().forEach(mappingInfo -> {
            HandlerMethod handlerMethod = map.get(mappingInfo);
            ResourcePermission method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), ResourcePermission.class);
            Optional.ofNullable(method)
                    .ifPresent(resourcePermission -> mappingInfo
                            .getPatternsCondition()
                            .getPatterns()
                            .forEach(url -> {
                                String strUrl = URLConvertUtil.capture(url);
                                String permission = URLConvertUtil.convert(url);
                                permissionEntities.add(PermissionEntityVO
                                        .builder()
                                        .name(method.value())
                                        .permission(permission)
                                        .serviceId(applicationName)
                                        .url(strUrl)
                                        .build());
                            }));
            ResourcePermission controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), ResourcePermission.class);
            Optional.ofNullable(controller)
                    .ifPresent(resourcePermission -> mappingInfo
                            .getPatternsCondition()
                            .getPatterns()
                            .forEach(url -> {
                                String strUrl = URLConvertUtil.capture(url);
                                String permission = URLConvertUtil.convert(url);
                                if(StrUtil.isNotBlank(permission)){
                                    permissionEntities.add(PermissionEntityVO
                                            .builder()
                                            .name(method.value())
                                            .permission(permission)
                                            .serviceId(applicationName)
                                            .url(strUrl)
                                            .build());
                                }
                            }));
        });
        if(CollUtil.isNotEmpty(permissionEntities)){
            permissionProducer.send(permissionEntities);
        }
    }
}

这里为什么使用了mq 把数据发出去?因为在微服务中,像这种代码不可能每个服务需要设置权限都重新写一套吧,所以要创建一个公共模块,然后把这代码写在公共模块中,其他服务需要配置权限时,直接依赖即可

3、把配置的权限交给资源服务
@Slf4j
@AllArgsConstructor
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    private final RemoteTokenServices remoteTokenServices;
    private final RestTemplate restTemplate;
    private final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;
    private final CustomAccessDeniedHandler customAccessDeniedHandler;
    private final AuthIgnoreConfig authIgnoreConfig;
    private final ResourcePermissionConfig resourcePermissionConfig;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        String[] urls = authIgnoreConfig.getIgnoreUrls().stream().distinct().toArray(String[]::new);
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
        registry.antMatchers(urls).permitAll();
        resourcePermissionConfig.getPermissionEntities().forEach(
                permissionEntity -> registry.antMatchers(permissionEntity.getUrl()).hasAnyRole(permissionEntity.getPermission()));
        registry.anyRequest().authenticated().and().csrf().disable();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {

        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        UserAuthenticationConverter userTokenConverter = new CustomUserAuthenticationConverter();
        accessTokenConverter.setUserTokenConverter(userTokenConverter);

        remoteTokenServices.setRestTemplate(restTemplate);
        remoteTokenServices.setAccessTokenConverter(accessTokenConverter);
        resources.authenticationEntryPoint(resourceAuthExceptionEntryPoint);
        resources.accessDeniedHandler(customAccessDeniedHandler);
        resources.tokenServices(remoteTokenServices);
    }
}
4、在认证服务中,监听MQ消息
@Slf4j
@Component
@RabbitListener(queues = RabbitMQConstants.PERMISSION_QUEUE)
@AllArgsConstructor
public class PermissionListener {

    private final SysPermissionService sysPermissionService;

    /**
     * 消息消费
     */
    @RabbitHandler
    public void recieved(List<PermissionEntityVO> list) {
        log.info("===========PermissionListener收到消息=============");
        if(ObjectUtil.isNotNull(list)){
            sysPermissionService.updateSysPermission(list);
        }
    }

}

MQ 收到消息后保存到数据库

5、在用户登录时,查询用户的所有权限,然后交给security-oauth2的框架管理
@Slf4j
@Service
@AllArgsConstructor
public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
    private final AuthenticationUserService customUserService;
    private final SysUserRoleMapper sysUserRoleMapper;
    private final SysPermissionMapper sysPermissionMapper;

    @Override
    @Cacheable(value = SecurityConstants.CACHE_USER_DETAILS, key = "#user_details", unless = "#result == null")
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = customUserService.loadUserByUsername(username);
        //查询用户有哪些角色
        List<Long> roleIds = sysUserRoleMapper
                .selectList(Wrappers.<SysUserRole>query().lambda().eq(SysUserRole::getUserId,sysUser.getUserId()))
                .stream()
                .map(SysUserRole::getRoleId)
                .collect(Collectors.toList());
        if(CollUtil.isNotEmpty(roleIds)){
            //根据角色查询权限
            List<String> roleCodes = sysPermissionMapper.getPermission(roleIds)
                    .stream()
                    .map(SysPermission::getPermission)
                    .collect(Collectors.toList());
            sysUser.setRoleCode(roleCodes);
        }
        UserDetails userDetails = UserDetailsUtils.getUserDetails(sysUser);
        return userDetails;
    }
}
6、最后在接口上添加权限注解
@ResourcePermission("调用sms服务")
@RequestMapping(value = "/sms",method = RequestMethod.GET)
public R getSms(){
    return remoteSmsService.hello();
}
在管理员配置角色的时候也可以在数据库选择

在这里插入图片描述

验证流程
  • 1、用户登录后查询所有的权限交给oauth2保存
  • 2、当用户访问某个服务的资源时,会去验证这个用户是否有访问该资源的权限。因为服务启动时,就把所有加上注解的url资源交给了资源服务器,所有用户访问资源时,资源服务器,会通过check_token 去认证服务器验证token 和验证是否有访问权限
  • 最后 项目地址:https://github.com/yzcheng90/ms

你可能感兴趣的:(MS系列,oauth2,权限)