/* * Copyright 2019-2020 Zheng Jie * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.zhengjie.security.config; import lombok.RequiredArgsConstructor; import me.zhengjie.annotation.AnonymousAccess; import me.zhengjie.security.config.bean.SecurityProperties; import me.zhengjie.security.security.TokenConfigurer; import me.zhengjie.security.security.TokenFilter; import me.zhengjie.security.security.TokenProvider; import me.zhengjie.security.service.OnlineUserService; import me.zhengjie.utils.enums.RequestMethodEnum; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.filter.CorsFilter; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.util.*; /** * @author Zheng Jie */ @Configuration @EnableWebSecurity @RequiredArgsConstructor @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { private final TokenProvider tokenProvider; private final CorsFilter corsFilter; // private final JwtAuthenticationEntryPoint authenticationErrorHandler; // private final JwtAccessDeniedHandler jwtAccessDeniedHandler; private final ApplicationContext applicationContext; private final SecurityProperties properties; private final OnlineUserService onlineUserService; @Bean GrantedAuthorityDefaults grantedAuthorityDefaults() { // 去除 ROLE_ 前缀 return new GrantedAuthorityDefaults(""); } @Bean public PasswordEncoder passwordEncoder() { // 密码加密方式 return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 搜寻匿名标记 url: @AnonymousAccess RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping"); Map handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods(); // 获取匿名标记 Map> anonymousUrls = getAnonymousUrl(handlerMethodMap); httpSecurity // 禁用 CSRF .csrf().disable() .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) // 授权异常 .exceptionHandling() // .authenticationEntryPoint(authenticationErrorHandler) // .accessDeniedHandler(jwtAccessDeniedHandler) // 防止iframe 造成跨域 .and() .headers() .frameOptions() .disable() // 不创建会话 .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 静态资源等等 .antMatchers( HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.png", "/**/*.css", "/**/*.ico", "/**/*.js", "/webSocket/**", "/appSocketServer/**", "/socket.io/**" // "/tencentpush", // "/apppush" ).permitAll() // swagger 文档 .antMatchers("/swagger-ui.html").permitAll() .antMatchers("/swagger-resources/**").permitAll() .antMatchers("/app/**").permitAll() .antMatchers("/api/bank-app/**").permitAll() .antMatchers("/minipro/**").permitAll() .antMatchers("/image/**").permitAll() .antMatchers("/webjars/**").permitAll() .antMatchers("/*/api-docs").permitAll() // 文件 .antMatchers("/avatar/**").permitAll() .antMatchers("/file/**").permitAll() .antMatchers("/static/**").permitAll() // 阿里巴巴 druid .antMatchers("/druid/**").permitAll() // 放行OPTIONS请求 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型 // GET .antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll() // POST .antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll() // PUT .antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll() // PATCH .antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll() // DELETE .antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll() // 所有类型的接口都放行 .antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll() // 所有请求都需要认证 .anyRequest().authenticated() .and().apply(securityConfigurerAdapter()); //设置所有的不需要权限访问的 TokenFilter.setAnonymousUrl(anonymousUrls); System.out.println(anonymousUrls); } private TokenConfigurer securityConfigurerAdapter() { return new TokenConfigurer(tokenProvider, properties, onlineUserService); } private Map> getAnonymousUrl(Map handlerMethodMap) { Map> anonymousUrls = new HashMap<>(8); Set get = new HashSet<>(); Set post = new HashSet<>(); Set put = new HashSet<>(); Set patch = new HashSet<>(); Set delete = new HashSet<>(); Set all = new HashSet<>(); for (Map.Entry infoEntry : handlerMethodMap.entrySet()) { HandlerMethod handlerMethod = infoEntry.getValue(); AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class); if (null != anonymousAccess) { List requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods()); RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name()); switch (Objects.requireNonNull(request)) { case GET: get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); break; case POST: post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); break; case PUT: put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); break; case PATCH: patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); break; case DELETE: delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); break; default: all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); break; } } } anonymousUrls.put(RequestMethodEnum.GET.getType(), get); anonymousUrls.put(RequestMethodEnum.POST.getType(), post); anonymousUrls.put(RequestMethodEnum.PUT.getType(), put); anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch); anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete); anonymousUrls.put(RequestMethodEnum.ALL.getType(), all); return anonymousUrls; } }