- 需求:客户端接口有的需要权限控制,有的不需要权限控制
- 技术栈:Spring Security
步骤一:自定义注解
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Import(AuthControlRegistrar.class)
@Component
public @interface ClientAuthControl {
String value() default "";
}
步骤二:扫描指定注解
@Slf4j
@Component
public class AuthControlRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
private static Set<BeanDefinition> candidateComponents = new HashSet<>();
public static Set<String> URL_NOT_AUTH = new HashSet<>();
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scan = new ClassPathScanningCandidateComponentProvider(false, this.environment);
scan.addIncludeFilter(new AnnotationTypeFilter(ClientAuthControl.class));
String packageName = ClassUtils.getPackageName(annotationMetadata.getClassName());
candidateComponents.addAll(scan.findCandidateComponents(packageName));
}
@SneakyThrows
@PostConstruct
private void getNotAuthInfo() {
for (BeanDefinition candidateComponent : candidateComponents) {
String beanClassName = candidateComponent.getBeanClassName();
Class<?> aClass = Class.forName(beanClassName);
RequestMapping annotation = aClass.getAnnotation(RequestMapping.class);
String classUrl = "";
if (annotation != null) {
String[] classUrls = annotation.value();
classUrl = arrToString(classUrls);
}
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(ClientAuthControl.class)){
continue;
}
String mode;
String url ;
RequestMapping requestMapping;
GetMapping getMapping;
PostMapping postMapping;
PutMapping putMapping;
DeleteMapping deleteMapping;
PatchMapping patchMapping;
if (null != (requestMapping = method.getAnnotation(RequestMapping.class))){
String[] value = requestMapping.value();
RequestMethod[] requestMethods = requestMapping.method();
mode = arrToString(requestMethods);
url = arrToString(value);
}else if (null != (getMapping = method.getAnnotation(GetMapping.class))) {
String[] value = getMapping.value();
mode = RequestMethod.GET.name();
url = arrToString(value);
}else if (null != (postMapping = method.getAnnotation(PostMapping.class))) {
String[] value = postMapping.value();
mode = RequestMethod.POST.name();
url = arrToString(value);
}else if (null != (putMapping = method.getAnnotation(PutMapping.class))) {
String[] value = putMapping.value();
mode = RequestMethod.PUT.name();
url = arrToString(value);
}else if (null != (deleteMapping = method.getAnnotation(DeleteMapping.class))) {
String[] value = deleteMapping.value();
mode = RequestMethod.DELETE.name();
url = arrToString(value);
}else if (null != (patchMapping = method.getAnnotation(PatchMapping.class))) {
String[] value = patchMapping.value();
mode = RequestMethod.PATCH.name();
url = arrToString(value);
}else continue;
if (!url.startsWith("/")) {
url = "/" + url;
}
if (url.contains("{") && url.contains("}")) {
url = url.substring(0,url.indexOf("{"));
}
url = classUrl + url;
URL_NOT_AUTH.add(String.format("%s_%s",mode,url));
}
}
}
private String arrToString(Object[] arr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
sb.append(arr[i].toString());
if (i != (arr.length -1)) {
sb.append(",");
}
}
return sb.toString();
}
步骤三:权限判断
@Slf4j
@Component
public class TokenOncePerRequestFilter extends OncePerRequestFilter {
@Resource
private JwtProperties jwtProperties;
@Resource
JwtUser jwtUser;
@Resource
private MyUserDetailsService myUserDetailsService;
private boolean notRequiresAuthentication(HttpServletRequest request) {
String uri = request.getRequestURI();
for (String s : START_WITH_PERMIT_ALL_URL){
if (uri.startsWith(s)) {
return true;
}
}
return false;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
BaseRes<String> res = new BaseRes<>(TOKEN_INVALID);
out: if (!this.notRequiresAuthentication(request)) {
String authToken = request.getHeader(jwtProperties.getHeader());
if (StringUtils.isBlank(authToken)) {
authToken = request.getParameter("access_token");
}
String tokenHead = "Bearer ";
String uri = request.getRequestURI();
String method = request.getMethod();
String urlFlag = String.format("%s_%s",method,uri);
for (String authUrl : AuthControlRegistrar.URL_NOT_AUTH) {
if (authUrl.contains(urlFlag) || urlFlag.contains(authUrl)) {
break out;
}
}
if (StringUtils.isBlank(authToken) || !authToken.startsWith(tokenHead)) {
log.error("token is null uri={}, url={}", request.getRequestURI(), request.getRequestURL());
CommUtils.printObjJson(response, res);
return;
}
authToken = authToken.substring(tokenHead.length());
try {
String rawJson = jwtUser.getClaimByToken(authToken);
TokenUser tokenUser = myUserDetailsService.loadUserByUsername(rawJson);
String type = tokenUser.getType();
String s = String.format("/%s", type).toLowerCase();
if (!uri.startsWith(s)) {
CommUtils.printObjJson(response, res);
return;
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(tokenUser, null, tokenUser.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
ExceptionUtils.setExceptionMsg(e, res);
CommUtils.printObjJson(response, res);
return;
}
}
filterChain.doFilter(request, response);
}
}
步骤四:因为只做了后台动态权限,客户端则需要把所有权限添加进去
public static final String CLIENT_START_URL = "/client";
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();
http.csrf().disable();
initAuth(http);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(CLIENT_START_URL + "/**").permitAll()
.anyRequest().authenticated()
;
http.addFilterAfter(tokenOncePerRequestFilter, SecurityContextPersistenceFilter.class);
http.exceptionHandling()
.authenticationEntryPoint(entryPointUnauthorizedHandler)
.accessDeniedHandler(restAccessDeniedHandler)
;
}
步骤五:添加注解到Controller 的类上和指定方法上
@RestController
@RequestMapping("/client/policy")
@ClientAuthControl
public class PartyPolicyClientController {
@Resource
private PartyPolicyService partyPolicyService;
@PostMapping("/list")
@ClientAuthControl
public BaseRes<IPage<PartyPolicyVo>> selectPartyPolicyList(@RequestBody PageMessage<PartyPolicyVo> pageMessage) {
return partyPolicyService.selectPartyPolicyList(pageMessage.getT(),pageMessage.getPage());
}
@GetMapping("/info")
@ClientAuthControl
public BaseRes<PartyPolicy> getPartyPolicyDetail(@RequestParam("id") Integer id) {
return partyPolicyService.getPartyPolicyDetail(id);
}
}
大功告成。客户端请求该接口时则跳过权限判断