SpringSecurity的核心功能
1.認證(你是誰) 2.授權(能幹嘛) 3.攻擊防護(防止僞造身份)
SpringSecurity的原理
在這條顧慮其鏈上,如果我們定義的是表單認證,那麼UsernamePasswordAuthenticationFilter就會起作用,如果是Basic認證,那麼BasicAuthenticationFilter就起作用,這條鏈上如果不相關的是不會起作用的。最後到達FilterSecurityInterceptor來判斷是否可以訪問REST API,如果不符合就拋出異常,此時ExceptionTranslationFilter起作用。
自定義用戶認證
處理用戶信息獲取邏輯
用戶的信息被封裝在了org.springframework.security.core.userdetails.UserDetailsService, 如果需要從自己的數據源獲取用戶數據,那麼就需要實現UserDetailsService類,並實現loadUserByUsername(String s)
@Component//TODO 使之成爲用戶的bean
public class MyDetailService implements UserDetailsService {
Logger logger = LoggerFactory.getLogger(MyDetailService.class);
[@Override](https://my.oschina.net/u/1162528)
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//TODO 根據用戶名查找用戶信息
logger.info("登陸用戶名:"+s);
return new User(s,"123456", AuthorityUtils.createAuthorityList("admin"));
}
}
處理用戶校驗邏輯
可以不使用SpringSecurity的User,可以自定義一個類實現UserDetail,實現它的下面的四個方法。 //賬戶是否過期 boolean isAccountNonExpired(); //賬戶是否被鎖 boolean isAccountNonLocked(); //證書是否過期 boolean isCredentialsNonExpired(); //是否可用 boolean isEnabled();
例子:
@Component//TODO 使之成爲用戶的bean
public class MyDetailService implements UserDetailsService {
Logger logger = LoggerFactory.getLogger(MyDetailService.class);
[@Override](https://my.oschina.net/u/1162528)
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//TODO 根據用戶名查找用戶信息
logger.info("登陸用戶名:"+s);
return new User(s,"123456",true,true,true,false, AuthorityUtils.createAuthorityList("admin"));
}
}
處理密碼加密解密
需要配置一個加密器:
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
//生成一個加密器
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
....}
@Component//TODO 使之成爲用戶的bean
public class MyDetailService implements UserDetailsService {
** @Autowired
PasswordEncoder passwordEncoder;**
Logger logger = LoggerFactory.getLogger(MyDetailService.class);
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//TODO 根據用戶名查找用戶信息
logger.info("登陸用戶名:"+s);
String encode = passwordEncoder.encode("123456");
logger.info("登陸用戶名:"+encode);
return new User(s,encode,true,true,true,true, AuthorityUtils.createAuthorityList("admin"));
}
}
執行結果:
2018-11-28 21:23:10.046 INFO 7560 --- [nio-8088-exec-3] com.flexible.service.MyDetailService : 登陸用戶名:user
2018-11-28 21:23:10.133 INFO 7560 --- [nio-8088-exec-3] com.flexible.service.MyDetailService : 登陸用戶名:$2a$10$S7UuQCqoIEnfzJJGZOnJuuavzZFlohoXA4IpGDkmxmmV9H.01XUIS
自定義認證流程
自定義的登錄界面
http.formLogin().loginPage("/imooc-signIn.html")//這裏可以寫登錄的界面的url,但是在之前的配置已經默認的攔截所以的url,所以會出現循環的打開登錄的界面,導致死循環,爲了解決這種情況就需要使用.antMatchers("/imooc-signIn.html").permitAll()//表示匹配到登錄的時候授權。此時在點擊任何該服務的鏈接就會跳到登錄界面,例如:
直接引導登錄界面
http.formLogin()
.loginPage("/imooc-signIn.html")
.and()
.authorizeRequests()//對後面的請求授權
.antMatchers("/imooc-signIn.html").permitAll()//表示匹配到登錄的時候授權。
.anyRequest()//任何請求
.authenticated();//都需要身份認證
實現自定義登錄界面
1.需要請求跳轉。
2.存儲跳轉前的請求到httpSessionRequest裏面。
3.將請求從緩存取出。
4.判斷請求跳轉時頁面跳轉還是其他的,如果頁面就可以時直接跳轉(但是要自定義跳轉的登錄頁面就需要需要實現登錄頁面時可以定製化的)。
5.鑑定權限,如果不是就登錄頁面的請求跳轉就是權限不夠提示401。
例子: SecurityProperties.java
@ConfigurationProperties(prefix = "flexible.security")
public class SecurityProperties {
private BrowserProperties browser = new BrowserProperties();
public BrowserProperties getBrowser() {
return browser;
}
public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
}
BrowserProperties.java
public class BrowserProperties {
//配置默認的登錄頁
private String loginPage="/imooc-signIn.html";
public BrowserProperties() {
}
public String getLoginPage() {
return loginPage;
}
public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
}
SecurityCoreConfig.java(讓配置生)
/**
* 讓我們配置的security配飾生效
*/
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
}
BrowserSecurityConfig.java(配置放行路徑跳轉到自己的路徑)
http.formLogin()
.loginPage("/authentication/require")//自定義的認證路徑
.loginProcessingUrl("/authentication/form")//告訴springsecurity使用UsernamePasswordAuthenticationFilter來處理登錄
.and()
.authorizeRequests()//對後面的請求授權,將自定義的登錄頁面頁放權
.antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage()).permitAll()//表示匹配到登錄的時候授權。
.anyRequest()//任何請求
.authenticated()//都需要身份認證
.and().csrf().disable();//將spring security爲了防止CSRF攻擊禁用
BrowserSecurityController.java
@RestController
public class BrowserSecurityController {
Logger logger = LoggerFactory.getLogger(BrowserSecurityController.class);
@Autowired
SecurityProperties securityProperties;//獲取到自定義的登錄頁
//拿到引發跳轉的請求需要通過HttpSessionRequestCache來拿
private RequestCache requestCache = new HttpSessionRequestCache();//在跳轉之前,springsecurity將請求緩存在這個httpSessionRequestCache裏面
//實現的跳轉的一個類
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/**
* 需要身份認證會跳轉到這裏
* @param request
* @param response
* @return
*/
@RequestMapping("authentication/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)//不是一個html就返回一個401的狀態碼
public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
//拿到在跳轉之前緩存的請求
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null) {
String target = savedRequest.getRedirectUrl();
logger.info("引發跳轉的請求是:{}", target);
if (StringUtils.endsWithIgnoreCase(target, ".html")) {
//跳轉,需要使用到RedirectStrategy
redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage());//這裏需要實現一個統一,通用的跳轉,不僅僅跳到自己的界面。
}
}
return new SimpleResponse("需要用戶認證,引導到登錄頁");
}
}
demo測試例子,在application.properties裏面配置一個flexible.security.browser.loginPage="xxx.html"就可以了。
註釋掉配置的flexible.security.browser.loginPage="xxx.html"就會跳轉默認的登錄頁。
自定義登錄成功處理
需要實現AuthenticationSuccessHandler接口
FlexibleAuthenticationHandler.java
@Component(value = "flexibleAuthenticationHandler")
public class FlexibleAuthenticationHandler implements AuthenticationSuccessHandler {
Logger logger = LoggerFactory.getLogger(FlexibleAuthenticationHandler.class);
@Autowired
ObjectMapper objectMapper;
/**
*
* @param httpServletRequest
* @param httpServletResponse
* @param authentication 封裝了認證請求的信息(ip,session,用戶信息)
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
logger.info("....login success...");
httpServletResponse.setContentType("application/json;charset=UTF-8");
//將authentication對象一json格式返回去
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
需要告訴SpringSecurity成功後的處理
.successHandler(flexibleAuthenticationHandler)//告訴SpringSecurity成功之後使用的處理器
自定義失敗處理
需要實現AuthenticationFailureHandler接口
FlexibleAuthenticationFailureHandler.java
@Component(value = "flexibleAuthenticationFailureHandler")
public class FlexibleAuthenticationFailureHandler implements AuthenticationFailureHandler {
Logger logger = LoggerFactory.getLogger(FlexibleAuthenticationFailureHandler.class);
@Autowired
ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authentication) throws IOException, ServletException {
logger.info("....login failure...");
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
//將authentication對象一json格式返回去
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
告訴SpringSecurity失敗後的處理器:
.failureHandler(flexibleAuthenticationFailureHandler)//告訴SpringSecurity失敗後的處理器