如何實現自定義的認證和受權
Spring Security
重寫WebSecurityConfigurerAdapter
方法能夠實現自定義的認證和受權,有兩種方式可用java
-
在
configure(HttpSecurity http)
方法種調用HttpSecurity
提供的3種認證方法開啓不一樣的認證方式web -
在
configure(HttpSecurity http)
方法種調用HttpSecurity
的apply()
方法,將自定義的SecurityConfigurerAdapter
(包含了你自定義的認證和受權)導入Spring Security
。app
ps:HttpSecurity
提供了ide
openidLogin()
基於 OpenId 的驗證formLogin()
基於表單的身份驗證oauth2Login()
基於外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份驗證
上述兩種自定義方式本質上都是調用了HttpSecurity
對象的apply()
方法。第一種方式實際上是將提早寫好的SecurityConfigurerAdapter
實現類做爲參數傳到apply()
方法種ui
關於WebSecurityConfigurerAdapter
和SecurityConfigurerAdapter
之後再說this
WebSecurityConfigurerAdapter
這裏簡單說一下WebSecurityConfigurerAdapter
url
經過重寫WebSecurityConfigurerAdapter
的三個configure()
方法實現自定以認證和受權.net
void configure(HttpSecurity http);
code
void configure(AuthenticationManagerBuilder auth);
orm
void configure(WebSecurity web);
一般重寫第一個方法
開啓並自定義表單登陸認知功能
重寫WebSecurityConfigurerAdapter
的configure(HttpSecurity http);
方法,調用http.formLogin()
@Override protected void configure(HttpSecurity http) throws Exception { // 關閉csrf http.csrf().disable(); // 建立一個SecurityConfigurerAdapter的子類,並將其應用到http中 http.apply(customUsernamePasswordAuthenticationConfig); http.formLogin() // 開啓表單登陸認證方式 .loginPage("/login") // 若是檢查到沒有登陸,就調用這個接口 .loginProcessingUrl("/login") // UserNamePasswordAuthenticationFilter默認攔截的url是login,這裏能夠自定義它攔截的url .defaultSuccessUrl("/successLogin") // 成功登陸後調用的接口(成功經過) // .successForwardUrl("/hello") // defaultSuccessUrl和successForwardUrl的區別參考 https://www.xttblog.com/?p=4994 // .successHandler() // 設置一個成功處理器,認證成功會執行此對象的方法 // .failureHandler() // 設置一個失敗處理器,認證失敗會執行此對象的方法 .permitAll() // 放行登陸接口 .and() .logout() // 開啓登出功能,建立DefaultLogoutPageGeneratingFilter .logoutUrl("/logout")// 設置執行登出服務的url .logoutRequestMatcher(getLogoutRequestMatcher()) // 默認登出只支持get方式的"/logout",這裏設置使用POST和GET方式的/logout都能響應登出 .logoutSuccessUrl("/successLogout") .and() // .rememberMe() // 建立相關過濾器,開啓記住我功能,可是須要表單提交一個name爲'remember-me'的單選框 // .tokenRepository() // 自定義token存儲業務 // .tokenValiditySeconds() // 自定義token存儲時長 // .and() .authorizeRequests() .antMatchers("/hello") .permitAll() // 放行/hello接口 .anyRequest() .authenticated(); // 其餘接口都保護起來 }
自定義認證流程
下面將模仿UsernamePasswordAuthenticationFilter
建立一套認證使用的組件
- 寫一個
Filter
過濾器,能夠繼承AbstractAuthenticationProcessingFilter
(默認只攔截POST
方式的'/login'的請求,具體見doFilter()
方法)也能夠繼承GenericFilterBean
- 寫
AuthenticationProvider
的實現類,這裏使用的」認證主體「實現類是內置的UsernamePasswordAuthenticationToken
,你也能夠自定義一個 - 寫一個
UserDetialsService
的實現類並注入到自定義的AuthenticationProvider
的實現類中 - 寫一個
SecurityConfigurerAdapter
的子類,將自定義的Filter
和AuthenticationProvider
加入到HttpSecurity
對象中
@Component public class CustomUsernamePasswordAuthenticationConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired private CustomUsernamePasswordAuthenticationProvider customUsernamePasswordAuthenticationProvider; // 這個方法用到的HttpSecurity對象和自定義的WebSecurityConfigurerAdapter的子類使用的HttpSecurity對象是同一個 @Override public void configure(HttpSecurity http) throws Exception { CustomUsernamePasswordAuthenticationFilter filter = new CustomUsernamePasswordAuthenticationFilter(); // 設置AuthenticaionManager,這裏獲取到的對象是http中保存的對象 filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); // 設置記住我服務 // filter.setRememberMeServices(); // 設置成功失敗處理器 // filter.setAuthenticationSuccessHandler(); // filter.setAuthenticationFailureHandler(); http // 將自定義的AuthenticationProvider加到http中,http會後會將其加入到AuthenticationManager中 // 既然是自定義的Filter其實也能夠不經過調用AuthenticationManager進行驗證,能夠直接將自定義的AuthenticationProvider注入到Filter中寫建立並校驗Authentication的流程 .authenticationProvider(customUsernamePasswordAuthenticationProvider) // 把自定義的過濾器放在UsernamePasswordAuthenticationFilter前 .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class); } }
public class CustomUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private static final String username = "username"; private static final String password = "password"; protected CustomUsernamePasswordAuthenticationFilter() { // 設置攔截器攔截/login super(new AntPathRequestMatcher("/login", "POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { // 獲取用戶名,密碼 String username = this.obtainUsername(request); String password = this.obtainPassword(request); // 判空 if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){ throw new UsernameNotFoundException("用戶名或密碼爲空"); } // 這裏可使用自定義AuthenticationToken UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); return this.getAuthenticationManager().authenticate(authenticationToken); } private String obtainPassword(HttpServletRequest request) { return request.getParameter(password); } private String obtainUsername(HttpServletRequest request) { return request.getParameter(username); } }
@Component public class CustomUsernamePasswordAuthenticationProvider implements AuthenticationProvider { // 自定義的UserDetailsService @Qualifier("customUserDetailsService") @Autowired private UserDetailsService userDetailsService; // 自定義的PasswordEncoder @Autowired private PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 獲取用戶名和密碼 String username = authentication.getPrincipal().toString(); String password = authentication.getCredentials().toString(); // 獲取user,一般自定義的UserDetailsService實現類會包含相似用username查詢user對象的邏輯 UserDetails user = userDetailsService.loadUserByUsername(username); if (user == null) { throw new InternalAuthenticationServiceException("找不到用戶"); } // 省略校驗user帳戶是否過時的流程 // 直接校驗密碼 if (!passwordEncoder.matches(password, user.getPassword())) { throw new BadCredentialsException("用戶名或密碼出錯"); } // 到這裏尚未出錯,就建立一個已經被認證的Authentication UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); return result; } @Override public boolean supports(Class<?> authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } }
自定義權限
一般將鑑權流程交給FilterSecurityInterceptor
就夠了,咱們要作的是賦予用戶不一樣的權限和爲接口設置權限
爲用戶賦予權限的流程在UserDetailsService
的UserDetails loadUserByUsername(String username)
方法中
建立一個User
對象,並將權限賦值給它,在用戶認證成功後便可完成權限賦值
爲接口設置權限有兩種方式
- Java-configure 寫Java代碼配置
- 註解配置
Java-configure配置
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/user").hasRole("ROLE_USER") // 有ROLE_USER權限的用戶才能訪問'/user'接口 .antMatchers("/admin").hasRole("ROLE_ADMIN") // 有ROLE_ADMIN權限的用戶才能訪問'/admin'接口 .anyRequest() .authenticated(); }
註解配置
- 在自定義的
WebSecurityConfigurerAdapter
的子類上添加@EnableGlobalMethodSecurity
開啓註解式配置 - 在接口方法上使用註解並指定訪問接口所須要的權限
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { }
@PreAuthorize("hasAnyRole('ROLE_USER')") @GetMapping("/user") @ResponseBody public UserDetails user(@AuthenticationPrincipal UserDetails userDetails){ return userDetails; } @PreAuthorize("hasAnyRole('ROLE_ADMIN')") @GetMapping("/admin") @ResponseBody public String admin(){ return "admin"; }
ps:關於hasRole()
方法和 hasAuthority()
方法的區別看這裏