SpringSecurity自定义注解放行,以及在微服务架构中使用
SpringSecurity的认证流程:(个人理解)
Spring Security通过一系列的过滤器组成的过滤链来处理安全相关的任务。在Web应用中,过滤器链主要用于实现身份验证、授权、记住我(Remember Me)等安全功能。
请求先到AuthenticationFilter,首先先验证使用的什么协议(只允许post请求),再获取账号密码,将账号密码封装成authentication,通常是封装成UsernamePasswordAuthenticationToken。注意此处有多种过滤器,BasicAuthenticationFilter,UsernamePasswordAuthenticationFilter,RememberMeAuthenticationFilter,SocialAuthenticationFilter,Oauth2AuthenticationProcessingFilter和Oauth2ClientAuthenticationProcessingFilter只有其中一个认证通过就会封装authentication对象并返回。
然后调用authenticationManager中的authentication方法进行认证,认证成功返回authentication,认证失败AuthenticationManager会根据不同的认证方式选择对应的Provider进行认证。
providerManage实现了AuthenticationManage的多种方法,通过调用其中的DaoAuthenticationprovider方法根据用户名加载用户信息,通过userDetail进行接收后封装为authentication对象并依次返回,返回到AuthenticationFilter时,通过AuthenticationManage进行认证,最后将主题信息返回到security的上下文中(就是保存Authentication对象),下次请求来的时候在securityContextPersistenceFilter中将Authentication拿出来,后续认证就不需要了。
下面说一下SpringSecurity自定义注解放行接口。
应用场景:实际项目开发中,会遇到需要放行一些接口,使其能匿名访问的业务需求。但是每当需要当需要放行时,都需要在security的配置类中进行修改,例如
// .antMatchers("captcha/check").anonymous()
感觉非常的不优雅。所以想通过自定义一个注解,来进行接口匿名访问。
首先创建一个自定义注解:
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface IgnoreAuth {
}
然后编写securityConfig类继承WebSecurityConfigurerAdapter:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private JwtAuthenticationTokenFilter filter;
@Resource
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Resource
private AccessDeniedHandler accessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问 未登录状态也可以访问
.antMatchers("/token/refreshToken").anonymous()
// 需要用户带有管理员权限
.antMatchers("/find").hasRole("管理员")
// 需要用户具备这个接口的权限
.antMatchers("/find").hasAuthority("menu:user")
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//添加过滤器
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理器
http.exceptionHandling()
//配置认证失败处理器
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
//允许跨域
http.cors();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* @ description: 使用这种方式放行的接口,不走 Spring Security 过滤器链,
* 无法通过 SecurityContextHolder 获取到登录用户信息的,
* 因为它一开始没经过 SecurityContextPersistenceFilter 过滤器链。
* @ dateTime: 2021/7/19 10:22
*/
}
自定义注解实现
说明:下面两种放行方式不能在有@ResquestMapper注解的接口上面使用,只能在@GetMapper,@PostMapper的接口中使用,因为我是通过请求方式进行放行的。
SpringSecurity提供了两种放行方式:
1.使用这种方式放行的接口,不走 Spring Security 过滤器链,
public void configure(WebSecurity web)
2.使用这种方式放行的接口,走 Spring Security 过滤器链,
protected void configure(HttpSecurity http)
1.使用走 Spring Security 过滤器链的放行方式
RequestMappingHandlerMapping组件可以获取系统中的所有接口,如图
我们可以对此进行遍历,获取携带了@IgnoreAuth的接口,再通过接口的请求方式进行放行
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
System.out.println("handlerMethods:" + handlerMethods);
handlerMethods.forEach((info, method) -> {
if (info.getMethodsCondition().getMethods().size() != 0) {
// 带IgnoreAuth注解的方法直接放行
if (!Objects.isNull(method.getMethodAnnotation(IgnoreAuth.class))) {
// 根据请求类型做不同的处理
info.getMethodsCondition().getMethods().forEach(requestMethod -> {
switch (requestMethod) {
case GET:
// getPatternsCondition得到请求url数组,遍历处理
info.getPathPatternsCondition().getPatterns().forEach(pattern -> {
// 放行
try {
http.authorizeRequests()
.antMatchers(HttpMethod.GET, pattern.getPatternString())
.anonymous();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
break;
case POST:
info.getPathPatternsCondition().getPatterns().forEach(pattern -> {
try {
http.authorizeRequests()
.antMatchers(HttpMethod.POST, pattern.getPatternString())
.anonymous();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
break;
case DELETE:
info.getPathPatternsCondition().getPatterns().forEach(pattern -> {
try {
http.authorizeRequests()
.antMatchers(HttpMethod.DELETE, pattern.getPatternString())
.anonymous();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
break;
case PUT:
info.getPathPatternsCondition().getPatterns().forEach(pattern -> {
try {
http.authorizeRequests()
.antMatchers(HttpMethod.PUT, pattern.getPatternString())
.anonymous();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
break;
default:
break;
}
});
}
}
});
需要注意的是,此处可能由于版本的不同,获取请求名称的方式有所不同。
这里通过pathPatternsCondition进行获取,某些版本需要在patternsCondition中进行获取,具体看个人的版本情况。
1.使用不走 Spring Security 过滤器链的放行方式
代码大体相同,都是首先获取所有接口,再进行遍历放行
WebSecurity and = web.ignoring().and();
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
// System.out.println("handlerMethods:" + handlerMethods);
handlerMethods.forEach((info, method) -> {
if (info.getMethodsCondition().getMethods().size() != 0) {
// 带IgnoreAuth注解的方法直接放行
if (!Objects.isNull(method.getMethodAnnotation(IgnoreAuth.class))) {
// 根据请求类型做不同的处理
info.getMethodsCondition().getMethods().forEach(requestMethod -> {
switch (requestMethod) {
case GET:
// getPatternsCondition得到请求url数组,遍历处理
info.getPathPatternsCondition().getPatterns().forEach(pattern -> {
// 放行
and.ignoring().antMatchers(HttpMethod.GET, pattern.getPatternString());
});
break;
case POST:
info.getPathPatternsCondition().getPatterns().forEach(pattern -> {
and.ignoring().antMatchers(HttpMethod.POST, pattern.getPatternString());
});
break;
case DELETE:
info.getPathPatternsCondition().getPatterns().forEach(pattern -> {
and.ignoring().antMatchers(HttpMethod.DELETE, pattern.getPatternString());
});
break;
case PUT:
info.getPathPatternsCondition().getPatterns().forEach(pattern -> {
and.ignoring().antMatchers(HttpMethod.PUT, pattern.getPatternString());
});
break;
default:
break;
}
});
}
}
});
}
在微服务架构中进行使用:
在微服务中,我们需要在不同的模块中实现单点登录,或使用到SpingSecurity的认证鉴权,或使用自己定义的放行注解。下面是我的解决方式。
项目模块为:
在springSecurity模块中配置了Jwt登录拦截,Redis,SpringSecurity配置等。
然后只需要在user-center中引入这个模块,这样就可以在user-center中使用配置好的功能。
在不同的模块中实现单点登录,或使用到SpingSecurity的认证鉴权,或使用自己定义的放行注解。下面是我的解决方式。
项目模块为:
[外链图片转存中…(img-VbrUll2N-1697356283528)]
在springSecurity模块中配置了Jwt登录拦截,Redis,SpringSecurity配置等。
[外链图片转存中…(img-AXwA9Aay-1697356283528)]
然后只需要在user-center中引入这个模块,这样就可以在user-center中使用配置好的功能。
文章仅为个人理解,欢迎指正。