SpringSecurity自定義注解放行以及在微服務(wù)架構(gòu)中使用詳解
SpringSecurity的認(rèn)證流程:(個(gè)人理解)
Spring Security通過(guò)一系列的過(guò)濾器組成的過(guò)濾鏈來(lái)處理安全相關(guān)的任務(wù)。在Web應(yīng)用中,過(guò)濾器鏈主要用于實(shí)現(xiàn)身份驗(yàn)證、授權(quán)、記住我(Remember Me)等安全功能。
請(qǐng)求先到AuthenticationFilter,首先先驗(yàn)證使用的什么協(xié)議(只允許post請(qǐng)求),再獲取賬號(hào)密碼,將賬號(hào)密碼封裝成authentication,通常是封裝成UsernamePasswordAuthenticationToken。注意此處有多種過(guò)濾器,BasicAuthenticationFilter,UsernamePasswordAuthenticationFilter,RememberMeAuthenticationFilter,SocialAuthenticationFilter,Oauth2AuthenticationProcessingFilter和Oauth2ClientAuthenticationProcessingFilter只有其中一個(gè)認(rèn)證通過(guò)就會(huì)封裝authentication對(duì)象并返回。
然后調(diào)用authenticationManager中的authentication方法進(jìn)行認(rèn)證,認(rèn)證成功返回authentication,認(rèn)證失敗AuthenticationManager會(huì)根據(jù)不同的認(rèn)證方式選擇對(duì)應(yīng)的Provider進(jìn)行認(rèn)證。
providerManage實(shí)現(xiàn)了AuthenticationManage的多種方法,通過(guò)調(diào)用其中的DaoAuthenticationprovider方法根據(jù)用戶名加載用戶信息,通過(guò)userDetail進(jìn)行接收后封裝為authentication對(duì)象并依次返回,返回到AuthenticationFilter時(shí),通過(guò)AuthenticationManage進(jìn)行認(rèn)證,最后將主題信息返回到security的上下文中(就是保存Authentication對(duì)象),下次請(qǐng)求來(lái)的時(shí)候在securityContextPersistenceFilter中將Authentication拿出來(lái),后續(xù)認(rèn)證就不需要了。
下面說(shuō)一下SpringSecurity自定義注解放行接口
應(yīng)用場(chǎng)景:實(shí)際項(xiàng)目開(kāi)發(fā)中,會(huì)遇到需要放行一些接口,使其能匿名訪問(wèn)的業(yè)務(wù)需求。但是每當(dāng)需要當(dāng)需要放行時(shí),都需要在security的配置類中進(jìn)行修改,例如
// .antMatchers("captcha/check").anonymous()
感覺(jué)非常的不優(yōu)雅。所以想通過(guò)自定義一個(gè)注解,來(lái)進(jìn)行接口匿名訪問(wèn)。
首先創(chuàng)建一個(gè)自定義注解:
@Target(ElementType.METHOD) //注解放置的目標(biāo)位置,METHOD是可注解在方法級(jí)別上
@Retention(RetentionPolicy.RUNTIME) //注解在哪個(gè)階段執(zhí)行
@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
//關(guān)閉csrf
.csrf().disable()
//不通過(guò)Session獲取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 對(duì)于登錄接口 允許匿名訪問(wèn) 未登錄狀態(tài)也可以訪問(wèn)
.antMatchers("/token/refreshToken").anonymous()
// 需要用戶帶有管理員權(quán)限
.antMatchers("/find").hasRole("管理員")
// 需要用戶具備這個(gè)接口的權(quán)限
.antMatchers("/find").hasAuthority("menu:user")
// 除上面外的所有請(qǐng)求全部需要鑒權(quán)認(rèn)證
.anyRequest().authenticated();
//添加過(guò)濾器
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
//配置異常處理器
http.exceptionHandling()
//配置認(rèn)證失敗處理器
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
//允許跨域
http.cors();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* @ description: 使用這種方式放行的接口,不走 Spring Security 過(guò)濾器鏈,
* 無(wú)法通過(guò) SecurityContextHolder 獲取到登錄用戶信息的,
* 因?yàn)樗婚_(kāi)始沒(méi)經(jīng)過(guò) SecurityContextPersistenceFilter 過(guò)濾器鏈。
* @ dateTime: 2021/7/19 10:22
*/
}
自定義注解實(shí)現(xiàn)
說(shuō)明:下面兩種放行方式不能在有@ResquestMapper注解的接口上面使用,只能在@GetMapper,@PostMapper的接口中使用,因?yàn)槲沂峭ㄟ^(guò)請(qǐng)求方式進(jìn)行放行的。
SpringSecurity提供了兩種放行方式
1.使用這種方式放行的接口,不走 Spring Security 過(guò)濾器鏈,
public void configure(WebSecurity web)
2.使用這種方式放行的接口,走 Spring Security 過(guò)濾器鏈,
protected void configure(HttpSecurity http)
使用走 Spring Security 過(guò)濾器鏈的放行方式
RequestMappingHandlerMapping組件可以獲取系統(tǒng)中的所有接口,如圖

我們可以對(duì)此進(jìn)行遍歷,獲取攜帶了@IgnoreAuth的接口,再通過(guò)接口的請(qǐng)求方式進(jìn)行放行
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))) {
// 根據(jù)請(qǐng)求類型做不同的處理
info.getMethodsCondition().getMethods().forEach(requestMethod -> {
switch (requestMethod) {
case GET:
// getPatternsCondition得到請(qǐng)求url數(shù)組,遍歷處理
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;
}
});
}
}
});
需要注意的是,此處可能由于版本的不同,獲取請(qǐng)求名稱的方式有所不同。

這里通過(guò)pathPatternsCondition進(jìn)行獲取,某些版本需要在patternsCondition中進(jìn)行獲取,具體看個(gè)人的版本情況。
使用不走 Spring Security 過(guò)濾器鏈的放行方式
代碼大體相同,都是首先獲取所有接口,再進(jìn)行遍歷放行
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))) {
// 根據(jù)請(qǐng)求類型做不同的處理
info.getMethodsCondition().getMethods().forEach(requestMethod -> {
switch (requestMethod) {
case GET:
// getPatternsCondition得到請(qǐng)求url數(shù)組,遍歷處理
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;
}
});
}
}
});
}
在微服務(wù)架構(gòu)中進(jìn)行使用
在微服務(wù)中,我們需要在不同的模塊中實(shí)現(xiàn)單點(diǎn)登錄,或使用到SpingSecurity的認(rèn)證鑒權(quán),或使用自己定義的放行注解。下面是我的解決方式。
項(xiàng)目模塊為:

在springSecurity模塊中配置了Jwt登錄攔截,Redis,SpringSecurity配置等。

然后只需要在user-center中引入這個(gè)模塊,這樣就可以在user-center中使用配置好的功能。
在不同的模塊中實(shí)現(xiàn)單點(diǎn)登錄,或使用到SpingSecurity的認(rèn)證鑒權(quán),或使用自己定義的放行注解。下面是我的解決方式。
項(xiàng)目模塊為:
在springSecurity模塊中配置了Jwt登錄攔截,Redis,SpringSecurity配置等。
然后只需要在user-center中引入這個(gè)模塊,這樣就可以在user-center中使用配置好的功能。

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中雙冒號(hào)(::)運(yùn)算操作符用法詳解
這篇文章主要給大家介紹了關(guān)于Java中雙冒號(hào)(::)運(yùn)算操作符用法的相關(guān)資料,雙冒號(hào)運(yùn)算操作符是類方法的句柄,lambda表達(dá)式的一種簡(jiǎn)寫,這種簡(jiǎn)寫的學(xué)名叫eta-conversion或者叫η-conversion,需要的朋友可以參考下2023-11-11
Tomcat使用IDEA遠(yuǎn)程Debug調(diào)試的講解
今天小編就為大家分享一篇關(guān)于Tomcat使用IDEA遠(yuǎn)程Debug調(diào)試的講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03
Java 隊(duì)列實(shí)現(xiàn)原理及簡(jiǎn)單實(shí)現(xiàn)代碼
這篇文章主要介紹了Java 隊(duì)列實(shí)現(xiàn)原理及簡(jiǎn)單實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10
Spring為什么要用三級(jí)緩存解決循環(huán)依賴呢
本文主要介紹了Spring如何使用三級(jí)緩存解決循環(huán)依賴問(wèn)題,本文為了方便說(shuō)明,先設(shè)置兩個(gè)業(yè)務(wù)層對(duì)象,命名為AService和BService,結(jié)合示例給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2025-01-01
Java基于字符流形式讀寫數(shù)據(jù)的兩種實(shí)現(xiàn)方法示例
這篇文章主要介紹了Java基于字符流形式讀寫數(shù)據(jù)的兩種實(shí)現(xiàn)方法示,結(jié)合實(shí)例形式分析了java逐個(gè)字符讀寫及使用緩沖區(qū)進(jìn)行讀寫操作的具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-01-01
springboot+websocket+redis搭建的實(shí)現(xiàn)
這篇文章主要介紹了springboot+websocket+redis搭建的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04

