增加 @PreAuthenticated 注解,实现登陆的拦截

This commit is contained in:
YunaiV 2021-09-28 09:18:51 +08:00
parent 0439a5505a
commit 689171c18d
9 changed files with 155 additions and 21 deletions

View File

@ -19,7 +19,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url> <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties> <properties>
<revision>1.0.0-snapshot</revision> <revision>1.1.0-snapshot</revision>
<!-- Maven 相关 --> <!-- Maven 相关 -->
<java.version>1.8</java.version> <java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.source>${java.version}</maven.compiler.source>

View File

@ -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<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.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;
}
}

View File

@ -19,7 +19,12 @@
<dependency> <dependency>
<groupId>cn.iocoder.boot</groupId> <groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId> <artifactId>yudao-common</artifactId>
<version>${revision}</version> </dependency>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency> </dependency>
<!-- Web 相关 --> <!-- Web 相关 -->

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.security.config; 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.filter.JwtAuthenticationTokenFilter;
import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl; import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl; import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
@ -32,6 +33,14 @@ public class YudaoSecurityAutoConfiguration {
@Resource @Resource
private SecurityProperties securityProperties; private SecurityProperties securityProperties;
/**
* 处理用户未登陆拦截的切面的 Bean
*/
@Bean
public PreAuthenticatedAspect preAuthenticatedAspect() {
return new PreAuthenticatedAspect();
}
/** /**
* 认证失败处理类 Bean * 认证失败处理类 Bean
*/ */

View File

@ -14,10 +14,12 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager; 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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@ -41,9 +43,6 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
@Resource @Resource
private WebProperties webProperties; private WebProperties webProperties;
@Value("${spring.boot.admin.context-path:''}")
private String adminSeverContextPath;
/** /**
* 自定义用户认证逻辑 * 自定义用户认证逻辑
*/ */
@ -74,6 +73,13 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
*/ */
@Resource @Resource
private JwtAuthenticationTokenFilter authenticationTokenFilter; private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 自定义的权限映射 Bean
*
* @see #configure(HttpSecurity)
*/
@Resource
private Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer;
/** /**
* 由于 Spring Security 创建 AuthenticationManager 对象时没声明 @Bean 注解导致无法被注入 * 由于 Spring Security 创建 AuthenticationManager 对象时没声明 @Bean 注解导致无法被注入
@ -121,15 +127,16 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
.csrf().disable() .csrf().disable()
// 基于 token 机制所以不需要 Session // 基于 token 机制所以不需要 Session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.headers().frameOptions().disable().and()
// 一堆自定义的 Spring Security 处理器 // 一堆自定义的 Spring Security 处理器
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler).and() .accessDeniedHandler(accessDeniedHandler).and()
// 设置每个请求的权限 .logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler); // 登出
.authorizeRequests()
// 设置每个请求的权限 全局共享规则
httpSecurity.authorizeRequests()
// 登陆的接口可匿名访问 // 登陆的接口可匿名访问
.antMatchers(api("/login")).anonymous() .antMatchers(api("/login")).anonymous()
// 通用的接口可匿名访问 TODO 芋艿需要抽象出去
.antMatchers(api("/system/captcha/**")).anonymous()
// 静态资源可匿名访问 // 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// 文件的获取接口可匿名访问 // 文件的获取接口可匿名访问
@ -139,22 +146,15 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
.antMatchers("/swagger-resources/**").anonymous() .antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous() .antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous() .antMatchers("/*/api-docs").anonymous()
// Spring Boot Admin Server 的安全配置 TODO 芋艿需要抽象出去
.antMatchers(adminSeverContextPath).anonymous()
.antMatchers(adminSeverContextPath + "/**").anonymous()
// Spring Boot Actuator 的安全配置 // Spring Boot Actuator 的安全配置
.antMatchers("/actuator").anonymous() .antMatchers("/actuator").anonymous()
.antMatchers("/actuator/**").anonymous() .antMatchers("/actuator/**").anonymous()
// Druid 监控 TODO 芋艿需要抽象出去 // Druid 监控 TODO 芋艿等对接了 druid admin 在调整下
.antMatchers("/druid/**").anonymous() .antMatchers("/druid/**").anonymous()
// 短信回调 API TODO 芋艿需要抽象出去 // 设置每个请求的权限 每个项目的自定义规则
.antMatchers(api("/system/sms/callback/**")).anonymous() .and().authorizeRequests(authorizeRequestsCustomizer)
.antMatchers(api("/user/**")).anonymous() // 设置每个请求的权限 兜底规则必须认证
// 除上面外的所有请求全部需要鉴权认证 .authorizeRequests().anyRequest().authenticated();
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler);
// 添加 JWT Filter // 添加 JWT Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
} }

View File

@ -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 {
}

View File

@ -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();
}
}

View File

@ -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<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer() {
return registry -> {
registry.antMatchers(api("/**")).anonymous(); // 默认 API 都是用户可访问
};
}
private String api(String url) {
return webProperties.getApiPrefix() + url;
}
}

View File

@ -1,6 +1,9 @@
package cn.iocoder.yudao.userserver.modules.infra.controller; package cn.iocoder.yudao.userserver.modules.infra.controller;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import lombok.extern.slf4j.Slf4j; 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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -15,4 +18,11 @@ public class HelloController {
public String hello(String hello) { public String hello(String hello) {
return "echo + " + hello; return "echo + " + hello;
} }
@RequestMapping("/user/info")
@PreAuthenticated
public String xx() {
return "none";
}
} }