作者:muggle
前言
由于第一版排版實(shí)在太過(guò)糟糕,而且很多細(xì)節(jié)沒(méi)交代清楚,所以決定寫(xiě)第二版;這一版爭(zhēng)取將排版設(shè)計(jì)得清晰明了一點(diǎn),以方便讀者閱讀。
security原理分析
springSecurity過(guò)濾器鏈
springSecurity 采用的是責(zé)任鏈的設(shè)計(jì)模式,它有一條很長(zhǎng)的過(guò)濾器鏈。現(xiàn)在對(duì)這條過(guò)濾器鏈的各個(gè)進(jìn)行說(shuō)明
WebAsyncManagerIntegrationFilter:將Security上下文與Spring Web中用于處理異步請(qǐng)求映射的 WebAsyncManager 進(jìn)行集成。
SecurityContextPersistenceFilter:在每次請(qǐng)求處理之前將該請(qǐng)求相關(guān)的安全上下文信息加載到SecurityContextHolder中,然后在該次請(qǐng)求處理完成之后,將SecurityContextHolder中關(guān)于這次請(qǐng)求的信息存儲(chǔ)到一個(gè)“倉(cāng)儲(chǔ)”中,然后將SecurityContextHolder中的信息清除
例如在Session中維護(hù)一個(gè)用戶的安全信息就是這個(gè)過(guò)濾器處理的。HeaderWriterFilter:用于將頭信息加入響應(yīng)中
CsrfFilter:用于處理跨站請(qǐng)求偽造
LogoutFilter:用于處理退出登錄
UsernamePasswordAuthenticationFilter:用于處理基于表單的登錄請(qǐng)求,從表單中獲取用戶名和密碼。默認(rèn)情況下處理來(lái)自“/login”的請(qǐng)求。從表單中獲取用戶名和密碼時(shí),默認(rèn)使用的表單name值為“username”和“password”,這兩個(gè)值可以通過(guò)設(shè)置這個(gè)過(guò)濾器的usernameParameter 和 passwordParameter 兩個(gè)參數(shù)的值進(jìn)行修改。
DefaultLoginPageGeneratingFilter:如果沒(méi)有配置登錄頁(yè)面,那系統(tǒng)初始化時(shí)就會(huì)配置這個(gè)過(guò)濾器,并且用于在需要進(jìn)行登錄時(shí)生成一個(gè)登錄表單頁(yè)面。
BasicAuthenticationFilter:檢測(cè)和處理http basic認(rèn)證
RequestCacheAwareFilter:用來(lái)處理請(qǐng)求的緩存
SecurityContextHolderAwareRequestFilter:主要是包裝請(qǐng)求對(duì)象request
AnonymousAuthenticationFilter:檢測(cè)SecurityContextHolder中是否存在Authentication對(duì)象,如果不存在為其提供一個(gè)匿名Authentication
SessionManagementFilter:管理session的過(guò)濾器
ExceptionTranslationFilter:處理 AccessDeniedException 和 AuthenticationException 異常
FilterSecurityInterceptor:可以看做過(guò)濾器鏈的出口
RememberMeAuthenticationFilter:當(dāng)用戶沒(méi)有登錄而直接訪問(wèn)資源時(shí), 從cookie里找出用戶的信息, 如果Spring Security能夠識(shí)別出用戶提供的remember me cookie, 用戶將不必填寫(xiě)用戶名和密碼, 而是直接登錄進(jìn)入系統(tǒng),該過(guò)濾器默認(rèn)不開(kāi)啟。
springSecurity 流程圖
上一版是通過(guò)debug的方法告訴讀者springSecurity的一個(gè)執(zhí)行過(guò)程,發(fā)現(xiàn)反而把問(wèn)題搞復(fù)雜了,這一版我決定畫(huà)一個(gè)流程圖來(lái)說(shuō)明其執(zhí)行過(guò)程,只要把springSecurity的執(zhí)行過(guò)程弄明白了,這個(gè)框架就會(huì)變得很簡(jiǎn)單

流程說(shuō)明
客戶端發(fā)起一個(gè)請(qǐng)求,進(jìn)入security過(guò)濾器鏈;
當(dāng)?shù)絃ogoutFilter的時(shí)候判斷是否是登出路徑,如果是登出路徑則到logoutHandler,如果登出成功則到logoutSuccessHandler登出成功處理,如果登出失敗則由ExceptionTranslationFilter;如果不是登出路徑則直接進(jìn)入下一個(gè)過(guò)濾器;
當(dāng)?shù)経sernamePasswordAuthenticationFilter的時(shí)候判斷是否為登陸路徑,如果是,則進(jìn)入該過(guò)濾器進(jìn)行登陸操作,如果登陸失敗則到AuthenticationFailureHandler登陸失敗處理器處理,如果登陸成功則到AuthenticationSuccessHandler登陸成功處理器處理 ;如果不是登陸請(qǐng)求則不進(jìn)入該過(guò)濾器
當(dāng)?shù)紽ilterSecurityInterceptor的時(shí)候會(huì)拿到urI,根據(jù)uri去找對(duì)應(yīng)的鑒權(quán)管理器,鑒權(quán)管理器做鑒權(quán)工作,鑒權(quán)成功則到controller層否則到AccessDeniedHandler鑒權(quán)失敗處理器處理
security配置
在WebSecurityConfigurerAdapter這個(gè)類里面可以完成上述流程圖的所有配置
配置類偽代碼
**/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**/*.html", "/resources/**/*.js");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login_page").passwordParameter("username").passwordParameter("password").loginProcessingUrl("/sign_in").permitAll()
.and().authorizeRequests().antMatchers("/test").hasRole("test")
.anyRequest().authenticated().accessDecisionManager(accessDecisionManager())
.and().logout().logoutSuccessHandler(new MyLogoutSuccessHandler())
.and().csrf().disable();
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
http.addFilterAfter(new MyFittler(), LogoutFilter.class);
}
}
配置類說(shuō)明
configure(AuthenticationManagerBuilder auth) 說(shuō)明
AuthenticationManager的建造器,配置AuthenticationManagerBuilder 會(huì)讓security自動(dòng)構(gòu)建一個(gè)AuthenticationManager(該類的功能參考流程圖);如果想要使用該功能你需要配置一個(gè)UserDetailService和passwordEncoder。userDetailsService用于在認(rèn)證器中根據(jù)用戶傳過(guò)來(lái)的用戶名查找一個(gè)用戶,passwordEncoder用于密碼的加密與比對(duì),我們存儲(chǔ)用戶密碼的時(shí)候用passwordEncoder.encode()加密存儲(chǔ),在認(rèn)證器里會(huì)調(diào)用passwordEncoder.matches()方法進(jìn)行密碼比對(duì)。
如果重寫(xiě)了該方法,security會(huì)啟用DaoAuthenticationProvider這個(gè)認(rèn)證器,該認(rèn)證就是先調(diào)用UserDetailsService.loadUserByUsername然后使用passwordEncoder.matches()進(jìn)行密碼比對(duì),如果認(rèn)證成功成功則返回一個(gè)Authentication對(duì)象
configure(WebSecurity web)說(shuō)明
這個(gè)配置方法用于配置靜態(tài)資源的處理方式,可使用ant匹配規(guī)則
configure(HttpSecurity http) 說(shuō)明
這個(gè)配置方法是最關(guān)鍵的方法,也是最復(fù)雜的方法。我們慢慢掰開(kāi)來(lái)說(shuō)
http.formLogin().loginPage("/login_page").passwordParameter("username").passwordParameter("password").loginProcessingUrl("/sign_in").permitAll()
這是配置登陸相關(guān)的操作從方法名可知,配置了登錄頁(yè)請(qǐng)求路徑,密碼屬性名,用戶名屬性名,和登陸請(qǐng)求路徑,permitAll()代表任意用戶可訪問(wèn)
http.authorizeRequests().antMatchers("/test").hasRole("test").anyRequest().authenticated().accessDecisionManager(accessDecisionManager());
以上配置是權(quán)限相關(guān)的配置,配置了一個(gè)“/test” url該有什么權(quán)限才能訪問(wèn),anyRequest()表示所有請(qǐng)求,authenticated()表示已登錄用戶,accessDecisionManager()表示綁定在url上的鑒權(quán)管理器
為了對(duì)比,現(xiàn)在貼出另一個(gè)權(quán)限配置清單
http.authorizeRequests().antMatchers("/tets_a/**","/test_b/**").hasRole("test").antMatchers("/a/**","/b/**").authenticated().accessDecisionManager(accessDecisionManager())
我們可以看到權(quán)限配置的自由度很高,鑒權(quán)管理器可以綁定到任意url上;而且可以硬編碼各種url權(quán)限;
http.logout().logoutUrl("/logout").logoutSuccessHandler(new MyLogoutSuccessHandler())
登出相關(guān)配置,這里配置了登出url和登出成功處理器
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
上面代碼是配置鑒權(quán)失敗的處理器
http.addFilterAfter(new MyFittler(), LogoutFilter.class);
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
上面代碼展示如何在過(guò)濾器鏈中插入自己的過(guò)濾器,addFilterBefore加在對(duì)應(yīng)的過(guò)濾器之前addFilterAfter之后,addFilterAt加在過(guò)濾器同一位置,事實(shí)上框架原有的Filter在啟動(dòng)HttpSecurity配置的過(guò)程中,都由框架完成了其一定程度上固定的配置,是不允許更改替換的。根據(jù)測(cè)試結(jié)果來(lái)看,調(diào)用addFilterAt方法插入的Filter,會(huì)在這個(gè)位置上的原有Filter之前執(zhí)行。
注:關(guān)于HttpSecurity使用的是鏈?zhǔn)骄幊?,其中http.xxxx.and.yyyyy這種寫(xiě)法和http.xxxx;http.yyyy寫(xiě)法意義一樣。
自定義authenticationManager和accessDecisionManager
重寫(xiě)authenticationManagerBean()方法,并構(gòu)造一個(gè)authenticationManager
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
ProviderManager authenticationManager = new ProviderManager(Arrays.asList(getMyAuthenticationProvider(),daoAuthenticationProvider()));
return authenticationManager;
}
我這里給authenticationManager配置了兩個(gè)認(rèn)證器,執(zhí)行過(guò)程參考流程圖
定義構(gòu)造AccessDecisionManager的方法并在配置類中調(diào)用,配置參考 configure(HttpSecurity http) 說(shuō)明
public AccessDecisionManager accessDecisionManager(){
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
new MyExpressionVoter(),
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter());
return new UnanimousBased(decisionVoters);
}
投票管理器會(huì)收集投票器投票結(jié)果做統(tǒng)計(jì),最終結(jié)果大于等于0代表通過(guò);每個(gè)投票器會(huì)返回三個(gè)結(jié)果:-1(反對(duì)),0(通過(guò)),1(贊成)。
security 權(quán)限用戶系統(tǒng)說(shuō)明
UserDetails
security中的用戶接口,我們自定義用戶類要實(shí)現(xiàn)該接口,各個(gè)屬性的含義自行百度
GrantedAuthority
security中的用戶權(quán)限接口,自定義權(quán)限需要實(shí)現(xiàn)該接口
@Data
public class MyGrantedAuthority implements GrantedAuthority {
private String authority;
}
authority權(quán)限字段,需要注意的是在config中配置的權(quán)限會(huì)被加上ROLE_前綴,比如我們的配置authorizeRequests().antMatchers("/test").hasRole("test"),配置了一個(gè)“test”權(quán)限但我們存儲(chǔ)的權(quán)限字段(authority)應(yīng)該是“ROLE_test”
UserDetailsService
security用戶service,自定義用戶服務(wù)類需要實(shí)現(xiàn)該接口
@Service
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return.....
}
}
loadUserByUsername的作用在上文中已經(jīng)說(shuō)明;
SecurityContextHolder
用戶在完成登陸后security會(huì)將用戶信息存儲(chǔ)到這個(gè)類中,之后其他流程需要得到用戶信息時(shí)都是從這個(gè)類中獲得,用戶信息被封裝成SecurityContext ,而實(shí)際存儲(chǔ)的類是SecurityContextHolderStrategy ,默認(rèn)的SecurityContextHolderStrategy 實(shí)現(xiàn)類是ThreadLocalSecurityContextHolderStrategy 它使用了ThreadLocal來(lái)存儲(chǔ)了用戶信息。
手動(dòng)填充SecurityContextHolder示例:
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test","test",list);
SecurityContextHolder.getContext().setAuthentication(token);
對(duì)于token鑒權(quán)的系統(tǒng)
我們就可以驗(yàn)證token后手動(dòng)填充SecurityContextHolder,填充時(shí)機(jī)只要在執(zhí)行投票器之前即可,或者干脆可以在投票器中填充,然后在登出操作中清空SecurityContextHolder。
security擴(kuò)展說(shuō)明
可擴(kuò)展的有
- 鑒權(quán)失敗處理器:security鑒權(quán)失敗默認(rèn)跳轉(zhuǎn)登陸頁(yè)面,我們可以
- 驗(yàn)證器
- 登陸成功處理器
- 投票器
- 自定義token處理過(guò)濾器
- 登出成功處理器
- 登陸失敗處理器
- 自定義UsernamePasswordAuthenticationFilter
鑒權(quán)失敗處理器
security鑒權(quán)失敗默認(rèn)跳轉(zhuǎn)登陸頁(yè)面,我們可以實(shí)現(xiàn)AccessDeniedHandler接口,重寫(xiě)handle()方法來(lái)自定義處理邏輯;然后參考配置類說(shuō)明將處理器加入到配置當(dāng)中
驗(yàn)證器
實(shí)現(xiàn)AuthenticationProvider接口來(lái)實(shí)現(xiàn)自己驗(yàn)證邏輯。需要注意的是在這個(gè)類里面就算你拋出異常,也不會(huì)中斷驗(yàn)證流程,而是算你驗(yàn)證失敗,我們由流程圖知道,只要有一個(gè)驗(yàn)證器驗(yàn)證成功,就算驗(yàn)證成功,所以你需要留意這一點(diǎn)
登陸成功處理器
在security中驗(yàn)證成功默認(rèn)跳轉(zhuǎn)到上一次請(qǐng)求頁(yè)面或者路徑為"/"的頁(yè)面,我們同樣可以自定義:繼承SimpleUrlAuthenticationSuccessHandler這個(gè)類或者實(shí)現(xiàn)AuthenticationSuccessHandler接口。我這里建議采用繼承的方式;SimpleUrlAuthenticationSuccessHandler是默認(rèn)的處理器,采用繼承可以契合里氏替換原則,提高代碼的復(fù)用性和避免不必要的錯(cuò)誤。
投票器
投票器可繼承WebExpressionVoter或者實(shí)現(xiàn)AccessDecisionVoter<FilterInvocation>接口;WebExpressionVoter是security默認(rèn)的投票器;我這里同樣建議采用繼承的方式;添加到配置的方式參考 配置類說(shuō)明章節(jié);
注意:投票器vote方法返回一個(gè)int值;-1代表反對(duì),0代表?xiàng)墮?quán),1代表贊成;投票管理器收集投票結(jié)果,如果最終結(jié)果大于等于0則放行該請(qǐng)求。
自定義token處理過(guò)濾器
自定義token處理器繼承自可OncePerRequestFilter或者GenericFilterBean或者Filter都可以,在這個(gè)處理器里面需要完成的邏輯是:獲取請(qǐng)求里的token,驗(yàn)證token是否合法然后填充SecurityContextHolder,雖然說(shuō)過(guò)濾器只要添加在投票器之前就可以;但我這里還是建議添加在http.addFilterAfter(new MyFittler(), LogoutFilter.class);
登出成功處理器
實(shí)現(xiàn)LogoutSuccessHandler接口,添加到配置的方式參考 配置類說(shuō)明章節(jié)
登陸失敗處理器
登陸失敗默認(rèn)跳轉(zhuǎn)到登陸頁(yè),我們同樣可以自定義。繼承SimpleUrlAuthenticationFailureHandler 或者實(shí)現(xiàn)AuthenticationFailureHandler;建議采用繼承。
自定義UsernamePasswordAuthenticationFilter
我們自定義UsernamePasswordAuthenticationFilter可以極大提高我們security的靈活性(比如添加驗(yàn)證驗(yàn)證碼是否正確的功能),所以我這里是建議自定義UsernamePasswordAuthenticationFilter;
我們直接繼承UsernamePasswordAuthenticationFilter,然后在配置類中初始化這個(gè)過(guò)濾器,給這個(gè)過(guò)濾器添加登陸失敗處理器,登陸成功處理器,登陸管理器,登陸請(qǐng)求url
這里配置略微復(fù)雜,貼一下代碼清單
初始化過(guò)濾器:
MyUsernamePasswordAuthenticationFilte getAuthenticationFilter(){
MyUsernamePasswordAuthenticationFilte myUsernamePasswordAuthenticationFilte = new MyUsernamePasswordAuthenticationFilte(redisService);
myUsernamePasswordAuthenticationFilte.setAuthenticationFailureHandler(new MyUrlAuthenticationFailureHandler());
myUsernamePasswordAuthenticationFilte.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
myUsernamePasswordAuthenticationFilte.setFilterProcessesUrl("/sign_in");
myUsernamePasswordAuthenticationFilte.setAuthenticationManager(getAuthenticationManager());
return myUsernamePasswordAuthenticationFilte;
}
添加到配置:
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
代碼清單
下面貼出適配于 前后端分離和token驗(yàn)證的偽代碼清單
登陸頁(yè)請(qǐng)求處理
@Controller
public class LoginController {
/**
* @Description: 登陸頁(yè)面的請(qǐng)求
* @Param:
* @return:
*/
@GetMapping("/login_page")
public String loginPage(){
return "loginPage.html";
}
}
鑒權(quán)失敗處理器
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("{\"code\":\"403\",\"msg\":\"沒(méi)有權(quán)限\"}");
writer.close();
}
}
驗(yàn)證器
public class MyAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public MyAuthenticationProvider(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 這里寫(xiě)驗(yàn)證邏輯
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
驗(yàn)證成功處理器
ublic class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//隨便寫(xiě)點(diǎn)啥
}
}
投票器
/**
* @program: security-test
* @description: 鑒權(quán)投票器
* @author: muggle
* @create: 2019-04-11
**/
public class MyExpressionVoter extends WebExpressionVoter {
@Override
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {
// 這里寫(xiě)鑒權(quán)邏輯
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
return 1 ;
}
}
自定義token處理過(guò)濾器
/**
* @program: security-about
* @description:填充一個(gè)token
* @author: muggle
* @create: 2019-04-20
**/
public class MyFittler extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token1 = request.getHeader("token");
if (token1==null){
}
ArrayList<GrantedAuthority> list = new ArrayList<>();
GrantedAuthority grantedAuthority = new GrantedAuthority() {
@Override
public String getAuthority() {
return "test";
}
};
list.add(grantedAuthority);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test","test",list);
SecurityContextHolder.getContext().setAuthentication(token);
filterChain.doFilter(request, response);
}
}
登出成功處理器
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
final PrintWriter writer = response.getWriter();
writer.write("{\"code\":\"200\",\"msg\":\"登出成功\"}");
writer.close();
}
}
登陸失敗處理器
public class MyUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
final PrintWriter writer = response.getWriter();
if(exception.getMessage().equals("壞的憑證")){
writer.write("{\"code\":\"401\",\"msg\":\"登錄失敗,用戶名或者密碼有誤\"}");
writer.close();
}else {
writer.write("{\"code\":\"401\",\"msg\":\"登錄失敗,"+exception.getMessage()+"\"}");
writer.close();
}
}
}
自定義UsernamePasswordAuthenticationFilter
/**
* @program: security-test
* @description: 用戶登陸邏輯過(guò)濾器
* @author: muggle
* @create: 2019-04-11
**/
public class MyUsernamePasswordAuthenticationFilte extends UsernamePasswordAuthenticationFilter {
private RedisService redisService;
private boolean postOnly = true;
public MyUsernamePasswordAuthenticationFilte(RedisService redisService){
this.redisService=redisService;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//你可以在這里做驗(yàn)證碼校驗(yàn),校驗(yàn)不通過(guò)拋出AuthenticationException()即可
super.attemptAuthentication(request,response);
}
}
配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
RedisService redisService;
@Autowired
MyUserDetailService userDetailService;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**/*.html", "/resources/**/*.js",
"/resources/**/*.css", "/resources/**/*.txt",
"/resources/**/*.png", "/**/*.bmp", "/**/*.gif", "/**/*.png", "/**/*.jpg", "/**/*.ico");
// super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置登錄頁(yè)等 permitAll表示任何權(quán)限都能訪問(wèn)
http.formLogin().loginPage("/login_page").passwordParameter("username").passwordParameter("password").loginProcessingUrl("/sign_in").permitAll()
.and().authorizeRequests().antMatchers("/test").hasRole("test")
// 任何請(qǐng)求都被accessDecisionManager() 的鑒權(quán)器管理
.anyRequest().authenticated().accessDecisionManager(accessDecisionManager())
// 登出配置
.and().logout().logoutUrl("/logout").logoutSuccessHandler(new MyLogoutSuccessHandler())
// 關(guān)閉csrf
.and().csrf().disable();
http.authorizeRequests().antMatchers("/tets_a/**","/test_b/**").hasRole("test").antMatchers("/a/**","/b/**").authenticated().accessDecisionManager(accessDecisionManager())
// 加自定義過(guò)濾器
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
// 配置鑒權(quán)失敗的處理器
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
http.addFilterAfter(new MyFittler(), LogoutFilter.class);
}
MyUsernamePasswordAuthenticationFilte getAuthenticationFilter(){
MyUsernamePasswordAuthenticationFilte myUsernamePasswordAuthenticationFilte = new MyUsernamePasswordAuthenticationFilte(redisService);
myUsernamePasswordAuthenticationFilte.setAuthenticationFailureHandler(new MyUrlAuthenticationFailureHandler());
myUsernamePasswordAuthenticationFilte.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
myUsernamePasswordAuthenticationFilte.setFilterProcessesUrl("/sign_in");
myUsernamePasswordAuthenticationFilte.setAuthenticationManager(getAuthenticationManager());
return myUsernamePasswordAuthenticationFilte;
}
MyAuthenticationProvider getMyAuthenticationProvider(){
MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider(userDetailService,new BCryptPasswordEncoder());
return myAuthenticationProvider;
}
DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
daoAuthenticationProvider.setUserDetailsService(userDetailService);
return daoAuthenticationProvider;
}
protected AuthenticationManager getAuthenticationManager() {
ProviderManager authenticationManager = new ProviderManager(Arrays.asList(getMyAuthenticationProvider(),daoAuthenticationProvider()));
return authenticationManager;
}
public AccessDecisionManager accessDecisionManager(){
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
new MyExpressionVoter(),
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter());
return new UnanimousBased(decisionVoters);
}
}
總結(jié)
對(duì)于security的擴(kuò)展配置關(guān)鍵在于configure(HttpSecurity http)方法;擴(kuò)展認(rèn)證方式可以自定義authenticationManager并加入自己驗(yàn)證器,在驗(yàn)證器中拋出異常不會(huì)終止驗(yàn)證流程;擴(kuò)展鑒權(quán)方式可以自定義accessDecisionManager然后添加自己的投票器并綁定到對(duì)應(yīng)的url(url 匹配方式為ant)上,投票器vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes)方法返回值為三種:-1 0 1,分別表示反對(duì)棄權(quán)贊成;
對(duì)于token認(rèn)證的校驗(yàn)方式,可以暴露一個(gè)獲取的接口,或者重寫(xiě)UsernamePasswordAuthenticationFilter過(guò)濾器和擴(kuò)展登陸成功處理器來(lái)獲取token,然后在LogoutFilter之后添加一個(gè)自定義過(guò)濾器,用于校驗(yàn)和填充SecurityContextHolder
security的處理器大部分都是重定向的,我們的項(xiàng)目如果是前后端分離的話,我們希望無(wú)論什么情況都返回json,那么就需要重寫(xiě)各個(gè)處理器了。