diff --git a/pom.xml b/pom.xml index 24c7d6741..7ca5191a1 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 1.0.0-snapshot + 1.1.0-snapshot 1.8 ${java.version} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/security/SecurityConfiguration.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/security/SecurityConfiguration.java new file mode 100644 index 000000000..8bb4bc9a6 --- /dev/null +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/framework/security/SecurityConfiguration.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.adminserver.framework.security; + +import cn.iocoder.yudao.framework.web.config.WebProperties; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; + +import javax.annotation.Resource; + +@Configuration +public class SecurityConfiguration { + + @Resource + private WebProperties webProperties; + + @Value("${spring.boot.admin.context-path:''}") + private String adminSeverContextPath; + + @Bean + public Customizer.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer() { + return registry -> { + // 通用的接口,可匿名访问 TODO 芋艿:需要抽象出去 + registry.antMatchers(api("/system/captcha/**")).anonymous(); + // Spring Boot Admin Server 的安全配置 TODO 芋艿:需要抽象出去 + registry.antMatchers(adminSeverContextPath).anonymous() + .antMatchers(adminSeverContextPath + "/**").anonymous(); + // 短信回调 API + registry.antMatchers(api("/system/sms/callback/**")).anonymous(); + }; + } + + private String api(String url) { + return webProperties.getApiPrefix() + url; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-security/pom.xml b/yudao-framework/yudao-spring-boot-starter-security/pom.xml index c62863bcb..45cc716b7 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-security/pom.xml @@ -19,7 +19,12 @@ cn.iocoder.boot yudao-common - ${revision} + + + + + org.springframework.boot + spring-boot-starter-aop diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java index b9e0f34fc..94b3ecd14 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.security.config; +import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect; import cn.iocoder.yudao.framework.security.core.filter.JwtAuthenticationTokenFilter; import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; @@ -32,6 +33,14 @@ public class YudaoSecurityAutoConfiguration { @Resource private SecurityProperties securityProperties; + /** + * 处理用户未登陆拦截的切面的 Bean + */ + @Bean + public PreAuthenticatedAspect preAuthenticatedAspect() { + return new PreAuthenticatedAspect(); + } + /** * 认证失败处理类 Bean */ diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java index b1e432d2e..893d2b0f5 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java @@ -14,10 +14,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -41,9 +43,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap @Resource private WebProperties webProperties; - @Value("${spring.boot.admin.context-path:''}") - private String adminSeverContextPath; - /** * 自定义用户【认证】逻辑 */ @@ -74,6 +73,13 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap */ @Resource private JwtAuthenticationTokenFilter authenticationTokenFilter; + /** + * 自定义的权限映射 Bean + * + * @see #configure(HttpSecurity) + */ + @Resource + private Customizer.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer; /** * 由于 Spring Security 创建 AuthenticationManager 对象时,没声明 @Bean 注解,导致无法被注入 @@ -121,15 +127,16 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap .csrf().disable() // 基于 token 机制,所以不需要 Session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .headers().frameOptions().disable().and() // 一堆自定义的 Spring Security 处理器 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler).and() - // 设置每个请求的权限 - .authorizeRequests() + .logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler); // 登出 + + // 设置每个请求的权限 ①:全局共享规则 + httpSecurity.authorizeRequests() // 登陆的接口,可匿名访问 .antMatchers(api("/login")).anonymous() - // 通用的接口,可匿名访问 TODO 芋艿:需要抽象出去 - .antMatchers(api("/system/captcha/**")).anonymous() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() // 文件的获取接口,可匿名访问 @@ -139,22 +146,15 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap .antMatchers("/swagger-resources/**").anonymous() .antMatchers("/webjars/**").anonymous() .antMatchers("/*/api-docs").anonymous() - // Spring Boot Admin Server 的安全配置 TODO 芋艿:需要抽象出去 - .antMatchers(adminSeverContextPath).anonymous() - .antMatchers(adminSeverContextPath + "/**").anonymous() // Spring Boot Actuator 的安全配置 .antMatchers("/actuator").anonymous() .antMatchers("/actuator/**").anonymous() - // Druid 监控 TODO 芋艿:需要抽象出去 + // Druid 监控 TODO 芋艿:等对接了 druid admin 后,在调整下。 .antMatchers("/druid/**").anonymous() - // 短信回调 API TODO 芋艿:需要抽象出去 - .antMatchers(api("/system/sms/callback/**")).anonymous() - .antMatchers(api("/user/**")).anonymous() - // 除上面外的所有请求全部需要鉴权认证 - .anyRequest().authenticated() - .and() - .headers().frameOptions().disable(); - httpSecurity.logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler); + // 设置每个请求的权限 ②:每个项目的自定义规则 + .and().authorizeRequests(authorizeRequestsCustomizer) + // 设置每个请求的权限 ③:兜底规则,必须认证 + .authorizeRequests().anyRequest().authenticated(); // 添加 JWT Filter httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/annotations/PreAuthenticated.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/annotations/PreAuthenticated.java new file mode 100644 index 000000000..901936dd7 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/annotations/PreAuthenticated.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.framework.security.core.annotations; + +import java.lang.annotation.*; + +/** + * 声明用户需要登陆 + * + * 为什么不使用 {@link org.springframework.security.access.prepost.PreAuthorize} 注解,原因是不通过时,抛出的是认证不通过,而不是未登陆 + * + * @author 芋道源码 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface PreAuthenticated { +} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/aop/PreAuthenticatedAspect.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/aop/PreAuthenticatedAspect.java new file mode 100644 index 000000000..808afc393 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/aop/PreAuthenticatedAspect.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.framework.security.core.aop; + +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; + +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; + +@Aspect +@Slf4j +public class PreAuthenticatedAspect { + + @Around("@annotation(preAuthenticated)") + public Object around(ProceedingJoinPoint joinPoint, PreAuthenticated preAuthenticated) throws Throwable { + if (SecurityFrameworkUtils.getLoginUser() == null) { + throw exception(UNAUTHORIZED); + } + return joinPoint.proceed(); + } + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/framework/security/SecurityConfiguration.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/framework/security/SecurityConfiguration.java new file mode 100644 index 000000000..e43f5e3e6 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/framework/security/SecurityConfiguration.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.userserver.framework.security; + +import cn.iocoder.yudao.framework.web.config.WebProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; + +import javax.annotation.Resource; + +@Configuration +public class SecurityConfiguration { + + @Resource + private WebProperties webProperties; + + @Bean + public Customizer.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer() { + return registry -> { + registry.antMatchers(api("/**")).anonymous(); // 默认 API 都是用户可访问 + }; + } + + private String api(String url) { + return webProperties.getApiPrefix() + url; + } + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/infra/controller/HelloController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/infra/controller/HelloController.java index 617228750..6a3270fc2 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/infra/controller/HelloController.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/infra/controller/HelloController.java @@ -1,6 +1,9 @@ package cn.iocoder.yudao.userserver.modules.infra.controller; +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -15,4 +18,11 @@ public class HelloController { public String hello(String hello) { return "echo + " + hello; } + + @RequestMapping("/user/info") + @PreAuthenticated + public String xx() { + return "none"; + } + }