diff --git a/eladmin-common/src/main/java/me/zhengjie/annotation/AnonymousAccess.java b/eladmin-common/src/main/java/me/zhengjie/annotation/AnonymousAccess.java new file mode 100644 index 00000000..46d5ab0c --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/annotation/AnonymousAccess.java @@ -0,0 +1,16 @@ +package me.zhengjie.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author jacky + * 用于标记匿名访问方法 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AnonymousAccess { + +} diff --git a/eladmin-common/src/main/java/me/zhengjie/config/ElPermissionConfig.java b/eladmin-common/src/main/java/me/zhengjie/config/ElPermissionConfig.java index a5b1aaf1..0a688cb1 100644 --- a/eladmin-common/src/main/java/me/zhengjie/config/ElPermissionConfig.java +++ b/eladmin-common/src/main/java/me/zhengjie/config/ElPermissionConfig.java @@ -1,6 +1,7 @@ package me.zhengjie.config; import me.zhengjie.utils.SecurityUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Service; import java.util.Arrays; @@ -11,6 +12,11 @@ import java.util.stream.Collectors; public class ElPermissionConfig { public Boolean check(String ...permissions){ + // 如果是匿名访问的,就放行 + String anonymous = "anonymous"; + if(Arrays.asList(permissions).contains(anonymous)){ + return true; + } List elPermissions = SecurityUtils.getUserDetails().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); List list = Arrays.stream(permissions).filter(elPermissions::contains).map(s -> s).collect(Collectors.toList()); if(elPermissions.contains("admin") || list.size() != 0){ diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/LimitController.java b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/LimitController.java index 00baee19..3e369061 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/LimitController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/monitor/rest/LimitController.java @@ -2,7 +2,9 @@ package me.zhengjie.modules.monitor.rest; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import me.zhengjie.annotation.AnonymousAccess; import me.zhengjie.annotation.Limit; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -17,12 +19,14 @@ import java.util.concurrent.atomic.AtomicInteger; @RequestMapping("/api/limit") @Api(tags = "系统:限流测试管理") public class LimitController { + private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(); /** * 测试限流注解,下面配置说明该接口 60秒内最多只能访问 10次,保存到redis的键名为 limit_test, */ @GetMapping + @PreAuthorize("@el.check('anonymous')") @ApiOperation("测试") @Limit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit") public int testLimit() { diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java index 000defb3..19a7efe5 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java @@ -1,13 +1,17 @@ package me.zhengjie.modules.security.config; +import me.zhengjie.annotation.AnonymousAccess; +import me.zhengjie.config.ElPermissionConfig; import me.zhengjie.modules.security.security.JwtAuthenticationEntryPoint; import me.zhengjie.modules.security.security.JwtAuthorizationTokenFilter; import me.zhengjie.modules.security.service.JwtUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +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.access.prepost.PreAuthorize; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; @@ -19,6 +23,13 @@ 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.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; @Configuration @EnableWebSecurity @@ -29,16 +40,19 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { private final JwtUserDetailsService jwtUserDetailsService; + private final ApplicationContext applicationContext; + // 自定义基于JWT的安全过滤器 private final JwtAuthorizationTokenFilter authenticationTokenFilter; @Value("${jwt.header}") private String tokenHeader; - public SecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtUserDetailsService jwtUserDetailsService, JwtAuthorizationTokenFilter authenticationTokenFilter) { + public SecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtUserDetailsService jwtUserDetailsService, JwtAuthorizationTokenFilter authenticationTokenFilter, ApplicationContext applicationContext) { this.unauthorizedHandler = unauthorizedHandler; this.jwtUserDetailsService = jwtUserDetailsService; this.authenticationTokenFilter = authenticationTokenFilter; + this.applicationContext = applicationContext; } @Autowired @@ -67,18 +81,26 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { - + // 搜寻 匿名标记 url: PreAuthorize("hasAnyRole('anonymous')") 和 PreAuthorize("@el.check('anonymous')") 和 AnonymousAccess + Map handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods(); + Set anonymousUrls = new HashSet<>(); + for (Map.Entry infoEntry : handlerMethodMap.entrySet()) { + HandlerMethod handlerMethod = infoEntry.getValue(); + AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class); + PreAuthorize preAuthorize = handlerMethod.getMethodAnnotation(PreAuthorize.class); + if (null != preAuthorize && preAuthorize.value().contains("anonymous")) { + anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); + } else if (null != anonymousAccess && null == preAuthorize) { + anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); + } + } httpSecurity - // 禁用 CSRF .csrf().disable() - // 授权异常 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() - // 不创建会话 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - // 过滤请求 .authorizeRequests() .antMatchers( @@ -88,31 +110,20 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { "/**/*.css", "/**/*.js" ).anonymous() - - .antMatchers(HttpMethod.POST,"/auth/login").permitAll() - .antMatchers(HttpMethod.DELETE,"/auth/logout").permitAll() - .antMatchers(HttpMethod.GET,"/auth/code").permitAll() - // 支付宝回调 - .antMatchers("/api/aliPay/return").permitAll() - .antMatchers("/api/aliPay/notify").permitAll() - // swagger start .antMatchers("/swagger-ui.html").permitAll() .antMatchers("/swagger-resources/**").permitAll() .antMatchers("/webjars/**").permitAll() .antMatchers("/*/api-docs").permitAll() // swagger end - - // 接口限流测试 - .antMatchers("/test/**").permitAll() // 文件 .antMatchers("/avatar/**").permitAll() .antMatchers("/file/**").permitAll() - // 放行OPTIONS请求 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() - .antMatchers("/druid/**").permitAll() + // 自定义匿名访问所有url放行 : 允许 匿名和带权限以及登录用户访问 + .antMatchers(anonymousUrls.toArray(new String[0])).permitAll() // 所有请求都需要认证 .anyRequest().authenticated() // 防止iframe 造成跨域 diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java index ef879856..c6be8204 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthenticationController.java @@ -5,6 +5,7 @@ import com.wf.captcha.ArithmeticCaptcha; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; +import me.zhengjie.annotation.AnonymousAccess; import me.zhengjie.aop.log.Log; import me.zhengjie.exception.BadRequestException; import me.zhengjie.modules.monitor.service.RedisService; @@ -58,6 +59,7 @@ public class AuthenticationController { @Log("用户登录") @ApiOperation("登录授权") + @AnonymousAccess @PostMapping(value = "/login") public ResponseEntity login(@Validated @RequestBody AuthUser authorizationUser, HttpServletRequest request){ @@ -96,6 +98,7 @@ public class AuthenticationController { } @ApiOperation("获取验证码") + @AnonymousAccess @GetMapping(value = "/code") public ImgResult getCode(){ // 算术类型 https://gitee.com/whvse/EasyCaptcha @@ -110,6 +113,7 @@ public class AuthenticationController { } @ApiOperation("退出登录") + @AnonymousAccess @DeleteMapping(value = "/logout") public ResponseEntity logout(HttpServletRequest request){ onlineUserService.logout(jwtTokenUtil.getToken(request)); diff --git a/eladmin-tools/src/main/java/me/zhengjie/rest/AliPayController.java b/eladmin-tools/src/main/java/me/zhengjie/rest/AliPayController.java index ce0add9f..95400d0e 100644 --- a/eladmin-tools/src/main/java/me/zhengjie/rest/AliPayController.java +++ b/eladmin-tools/src/main/java/me/zhengjie/rest/AliPayController.java @@ -3,6 +3,7 @@ package me.zhengjie.rest; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; +import me.zhengjie.annotation.AnonymousAccess; import me.zhengjie.aop.log.Log; import me.zhengjie.domain.AlipayConfig; import me.zhengjie.domain.vo.TradeVo; @@ -74,6 +75,7 @@ public class AliPayController { @ApiIgnore @GetMapping("/return") + @AnonymousAccess @ApiOperation("支付之后跳转的链接") public ResponseEntity returnPage(HttpServletRequest request, HttpServletResponse response){ AlipayConfig alipay = alipayService.find(); @@ -96,6 +98,7 @@ public class AliPayController { @ApiIgnore @RequestMapping("/notify") + @AnonymousAccess @ApiOperation("支付异步通知(要公网访问),接收异步通知,检查通知内容app_id、out_trade_no、total_amount是否与请求中的一致,根据trade_status进行后续业务处理") public ResponseEntity notify(HttpServletRequest request){ AlipayConfig alipay = alipayService.find();